1. 网络模型

理解【网络分层模型】是掌握【网络通信】的关键。TCP/IP四层模型(实际标准)和 OSI七层模型(理论模型)并非替代关系,而是相辅相成。

TCP/IP四层模型 (实用派) OSI七层模型 (理论派) 核心功能与比喻 典型协议/设备
应用层
(Application)
应用层
(Application)
表示层
(Presentation)
会话层
(Session)
处理具体应用逻辑
“做什么?”
(如收发邮件、浏览网页)
HTTP, FTP, SMTP, DNS
传输层
(Transport)
传输层
(Transport)
提供端到端的可靠或不可靠数据传输
“对方收到了吗?”
TCP(可靠), UDP(不可靠)
网络层
(Internet)
网络层
(Network)
负责寻址和路由,将数据包从源主机送到目标主机。
“走哪条路?”
IP, ICMP, 路由器
网络接口层
(Link)
数据链路层
(Data Link)
物理层
(Physical)
负责在本地网络(如一个局域网内)传输数据帧,处理物理信号
“每一步怎么走?”
Ethernet, Wi-Fi, 交换机, 网卡

1.1 为什么常用“四层”或“五层”,而不是“七层”?🤔

核心原因在于 TCP/IP协议栈 是实际发展起来的,而 OSI模型 是 后期统一的理论规划

  1. TCP/IP模型是“实践出真知”:它源于互联网前身ARPANET的实践,将OSI中上三层(应用、表示、会话)的复杂功能合并为了一个简洁的 “应用层”。因为在实际开发中,这些功能(如数据加密、会话管理)很难、也没有必要严格分离,通常由应用程序一体化实现(比如,浏览器)。

  2. “五层模型”是教学折中:在学习时,为了更清晰地讲解,常将TCP/IP的 网络接口层 拆分成OSI的 数据链路层物理层,形成折中的 五层模型。这既保留了TCP/IP的实用性,又借用了OSI下两层的理论清晰度。

  3. 为什么不是六层?历史上曾有过其他分层建议,但未被广泛接受。分层并非越多越好,过多的层级会导致协议复杂、效率降低。OSI七层本身已经常被诟病有些层(特别是表示层和会话层)定义模糊、功能重叠,在实践中极少有独立协议实现

1.2 核心思想💎

  • 核心思想分层是为了 “解耦”每一层只关心自己的任务,使用下层服务,并为上层提供接口。这极大简化了网络系统的设计、实现和排错。
  • 运维常用语:在数据中心和云网络里,常听到 “二层网络”(指交换机工作的数据链路层,负责MAC地址和VLAN)和 “三层网络”(指路由器工作的网络层,负责IP路由),这些术语正源于此模型。
  • 模型只是工具

2. Spring Cloud微服务通信中的协议栈(以RESTful为例)

现在我们抛弃理论,结合 Spring Boot + Spring Cloud 的微服务调用,看看一个数据包从 “想法” 变成比特流 的 实战穿越记

假设有两个服务:订单服务用户服务。订单服务 需要通过 RESTful API(HTTP) 调用用户服务的一个接口。

我们以 TCP/IP四层模型 为路线图,追踪一次 GET http://user-service/api/user/123 调用的完整旅程。


2.1 发送端:订单服务的“层层包装”📡

1. 应用层:业务代码与Spring Cloud

  • 是谁:我们的Java代码、Spring MVCSpring Cloud OpenFeignRibbon、服务注册中心(如Nacos/Eureka)。
  • 干什么
    • 我们写的 userService.getUser(123) 被Feign代理拦截。
    • Feign根据 @FeignClient("user-service") 中的服务名,去 服务注册中心 查找到 “用户服务” 的 真实IP地址和端口列表(例如 192.168.1.101:8080)。
    • Ribbon负载均衡器 从列表中选一个实例。
    • Feign将调用 翻译 成一个具体的HTTP请求报文:
      GET /api/user/123 HTTP/1.1
      Host: user-service
      Content-Type: application/json
      
  • 输出:一个符合HTTP协议规则的 文本报文。这个报文就是 应用层的“数据”,它将被交给下一层处理。

2. 传输层:操作系统的TCP协议栈

  • 是谁:服务器上的 操作系统内核(具体说是TCP/IP协议栈)。Java Socket API(包括Netty)是和应用层的接口。
  • 干什么
    • 收到来自Feign(最终通过JDK的HttpURLConnection或Netty客户端)的HTTP报文。
    • 建立TCP连接:操作系统与目标 192.168.1.101:8080 进行“三次握手”(如果连接池中没有可用长连接)。
    • 封装数据段:将HTTP报文 切割 成适合传输的 “块”,在每个块前面加上一个 TCP 头部。这个头部至关重要,它包含了:
      • 源端口目标端口8080)。正是这个 端口,告诉目标服务器的操作系统,这个数据应该交给哪个 应用程序进程(这里是用户服务的Spring Boot应用)。
      • 序列号确认号:用于保证数据可靠、有序。
      • 标志位(如ACK, SYN)。
  • 输出TCP 数据段。它包含了应用层的数据,并加上了 “端口寻址” 和 “可靠性保证” 信息。

3. 网络层:操作系统内核与路由器

  • 是谁:操作系统内核、服务器上的 路由表Docker网桥(如果服务容器化)、物理路由器
  • 干什么
    • 给 TCP 数据段加上一个 IP 头部。这个头部的核心是:
      • 源IP地址(例如 192.168.1.100,订单服务的IP)。
      • 目标IP地址192.168.1.101,用户服务的IP)。
    • 查路由表:操作系统查看目标IP是否在同一局域网。如果是,直接发送;如果不是,则发给 默认网关(路由器)。在这个微服务内网中,通常通过Docker网桥或K8s的Service网络进行寻址。
  • 输出IP 数据包。它加上了全局网络寻址(IP地址)信息,可以在复杂的网络间“路由”。

4. 网络接口层:网卡驱动程序

  • 是谁:服务器 网卡 及其 驱动程序
  • 干什么
    • 给 IP 数据包再加上一个 以太网帧头帧尾
    • 帧头里最重要的是 目标MAC地址。为了获得它,系统会发送 ARP 广播:“IP地址是 192.168.1.101 的兄弟,你的MAC地址是多少?” 目标服务器或网关会回应。
    • 将封装好的 以太网帧 转换成 电信号光信号,通过网线或光纤发送出去。
      • 物理本质:在网线(双绞线)中,【通过电压的变化来传输数据】。例如,在经典的10BASE-T以太网中,+2.5V左右的电压可能代表 “1”,-2.5V左右代表 “0”。在光纤中,则用光的亮灭(有光脉冲/无光脉冲)来表示。
  • 输出:物理链路上的 比特流。这里的 比特流 指的就是一连串由物理信号表示的二进制位(0和1)的序列。

2.2 接收端:用户服务的“层层拆包”📥

数据到达用户服务所在的服务器,过程完全逆序。

1. 网络接口层:网卡

  • 是谁:用户服务的服务器网卡。
  • 干什么:网卡接收到比特流,识别以太网帧,检查帧头上的 目标MAC地址 是不是自己。如果是,就去掉帧头和帧尾,将里面的 IP 数据包 交给操作系统内核的网络层驱动。

2. 网络层:操作系统内核

  • 是谁:用户服务的操作系统内核。
  • 干什么:检查 IP 头部 里的 目标IP地址 是不是自己。如果是,就去掉IP头部,将里面的 TCP 数据段 交给传输层处理程序。

3. 传输层:操作系统内核

  • 是谁:用户服务的操作系统内核。
  • 干什么:检查 TCP 头部 里的 目标端口8080)。操作系统知道,端口 8080 正被一个监听中的进程(用户服务的Spring Boot应用)使用。内核根据端口号,将数据段交给这个 特定的用户进程。如果数据段有序,内核会进行ACK确认。

4. 应用层:Spring Boot应用

  • 是谁:用户服务的JVM进程、内嵌的 Tomcat/Netty(Web服务器)、Spring MVC DispatcherServlet
  • 干什么
    • Tomcat/Netty 监听着端口 8080,它从操作系统的Socket中读取到 原始的HTTP请求报文(文本)
    • Tomcat/Netty 解析HTTP报文,根据 Host 和路径 /api/user/123,将请求交给 Spring MVC 处理。
    • DispatcherServlet 查找匹配的 @RestController@GetMapping,最终调用到我们写的 UserController.getUser(123) 方法。
    • 我们的方法返回一个Java对象,Spring MVC通过 Jackson 库将其 序列化 为JSON字符串,并交给Tomcat/Netty。
    • Tomcat/Netty 将这个HTTP响应(HTTP/1.1 200 OK 加上JSON body)写回 给操作系统的Socket。

然后,这个HTTP响应,将作为新的 “应用层数据”,沿着完全相反的路径(用户服务->网络各层->订单服务),被发送回订单服务,完成一次完整的交互。


2.3 一句话总结各层角色💎

  • 应用层Spring Boot代码和Spring Cloud组件。负责 业务逻辑服务间调用协议(HTTP/RPC报文生成与解析)。
  • 传输层操作系统内核。通过 端口 实现 进程到进程 的可靠/不可靠通信。
  • 网络层操作系统内核 + 网络设备。通过 IP地址 实现 主机到主机 的跨网络寻址和路由。
  • 网络接口层网卡驱动 + 物理设备。通过 MAC地址 实现 设备到设备 的本地网络帧传输。

Spring Cloud 架构中,Netty 可能扮演两个角色:

    1. 作为Tomcat的底层NIO实现,处理HTTP(应用层协议);
    1. 作为像 Spring Cloud Gateway 或 某些RPC框架 的底层网络库,直接处理传输层及以下的I/O。而像 RPC(如Dubbo/gRPC),只是把 应用层的协议 从HTTP【换(不是 “转换”)】成了更高效的二进制协议,底层的三层封装(TCP->IP->Ethernet)完全不变
层级 RESTful (HTTP) RPC (如gRPC)
应用层 HTTP协议,JSON格式 自定义协议,Protocol Buffers
传输层 TCP TCP
网络层 IP地址 IP地址
网络接口层 以太网 以太网
特点 灵活、跨语言、标准 高效、序列化紧凑、需要客户端/服务端约定

3. Socket(套接字)


3.1 Socket的本质

  • Socket(套接字)是 操作系统 提供给 应用层 的【编程接口(API)】,是【应用层】与【传输层】之间的 桥梁,让应用程序能通过网络发送和接收数据。
  • Socket(套接字)上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,也是应用程序与网络协议栈进行交互的接口。

3.2 Socket与TCP的关系

  • Socket(套接字)是操作 TCP/IP协议 的接口,通过 Socket API,应用程序可以操作 传输层的 TCP(或UDP)协议。
  • Socket 可以看作是一种特殊的文件,用于实现进程间的通信。在网络编程中,Socket 可以用来建立网络连接、发送和接收数据。

3.3 操作系统、Java与框架的三层抽象

  1. 操作系统层(如Windows、Linux):提供最原始的 Socket API(如C语言的send()、recv()、bind()、listen()、accept()等)。它直接与内核的TCP/IP栈交互,处理的是 最原始的字节流

  2. Java标准库层:Java作为跨平台语言,对 操作系统 提供的 Socket API 进行了 封装,提供了 java.net.Socketjava.net.ServerSocket 等类。它让Java程序可以用统一的方式调用网络功能,但操作的数据依然是 字节流或字符流

    • java.net.Socket:代表客户端连接到服务器上的一个实际的 socket。
    • java.net.ServerSocket:代表服务器端的一个 socket,用于监听客户端的连接请求。
    • Socket编程 直接操作 TCP
  3. 应用框架层:这是 应用层协议 发生的地方。

    • Tomcat (Servlet容器) / Netty (HTTP): 它们从Java的Socket中读取字节流,然后 按照HTTP协议规范(RFC标准)进行解析,将 “GET /api/user HTTP/1.1 Host: ...” 这堆文本,封装成我们所熟悉的 HttpServletRequest 对象。这个过程叫 解析反序列化。发送时,把我们设置的状态码、Header、JSON Body,按照HTTP协议格式拼接成文本字节流,再通过Java的Socket写回。它不参与网络传输,只负责“格式翻译”。
    • RPC框架 (如gRPC, Dubbo): 它们做的事情 本质上和Tomcat一样,只是换了一种 “对话规则”
  4. 所有网络通信 最终都是通过 操作系统 提供的 Socket API 实现的


4. 应用层

4.1 应用层协议的本质💎

  • 应用层协议(HTTP/RPC)的本质,就是 在 Socket 提供的 双向字节流通道 之上,【约定】一套双方都能 理解的 结构化数据格式和交换顺序。它不关心数据包是如何路由、如何确认的,它只关心 数据的内容、格式和交换逻辑。HTTP协议、RPC协议、数据库驱动协议(如,MySQL协议),都是这样的 “对话规则”。

  • HTTP把格式定为“文本头+空行+Body”,RPC把格式定为“二进制头+Protobuf Body”。它们都只是在 利用 传输层(TCP)的可靠管道,自己并不管管道是怎么铺设的。这正是网络分层的魅力:每一层各司其职,下层为上层服务,上层无需关心下层实现


4.2 HTTP与RPC🎯

4.2.1 基本概念

  1. RPC 是什么?

    • RPC 是一个概念、一个【框架】、一种【通信范式】。它的目标是:让开发者像调用本地函数一样去调用远程的服务,隐藏底层网络通信的复杂性。
    • RPC本身不规定具体的报文格式、传输方式、序列化机制。它本身不是一个具体的、像HTTP 那样有固定报文格式的协议,而是一个 架构理念。为了实现这个理念,需要一套完整的组件,包括:序列化协议、传输协议、服务发现、负载均衡等。
  2. HTTP 是什么?

    • HTTP 是一个 具体的、无状态的、应用层的通信协议。它规定了请求和响应的标准格式(起始行、头部、正文),定义了方法、状态码、缓存机制等。
    • HTTP是一种 具体的实现方式,是信息在网络上传输的 “语言” 和 “规则” 之一。

为了实现RPC的目标,我们需要选择一个 传输协议 来承载 “调用哪个函数、传递什么参数、返回什么结果” 这些信息。


4.2.2 RCP的实现方式

RPC 可以基于 HTTP,也可以直接基于 TCP(绕过 HTTP 通过 Socket 去操作 TCP),如下所示:

  1. 基于HTTP的RPC:这是最常见的形式。

    • 例如:
      • gRPC 使用 HTTP/2 作为传输协议。
      • 很多传统的 WebService 使用 HTTP + SOAP 来实现RPC。
      • Restful API 有时也被认为是一种基于HTTP的、资源化风格的RPC。当你调用 GET /api/users/1 时,本质上就是在远程获取用户数据。
    • 在这种情况下,HTTP是RPC的底层传输协议。RPC的请求和响应数据(经过序列化后)被放在HTTP协议的 Body 中进行传输。
    • 优点:兼容性好、可穿透防火墙、易调试。
    • 缺点:HTTP 协议头冗余,性能较低
  2. 不使用HTTP的RPC

    • 为了追求极致的性能,很多RPC框架会自定义更高效的二进制协议,直接在TCP层之上进行通信。例如:
      • Dubbo 协议(阿里巴巴,Java生态),Dubbo框架 默认使用自定义的 【Dubbo 协议(基于 Netty + TCP)】
      • Apache Thrift 的原生协议(Facebook 脸书,跨语言)。
      • Finagle协议 (Twitter,推特)。
      • Brpc协议 (百度,C++)。
      • gRPC 虽然用HTTP/2,但其数据编码是二进制的Protobuf,且完全利用了HTTP/2的多路复用等特性,与大家通常理解的“HTTP API”不同。
    • 这些协议通常比HTTP更紧凑、延迟更低,但通用性(如穿透防火墙、浏览器支持)不如HTTP。
    • 优点高性能、低延迟、自定义协议更紧凑。
    • 缺点:需要专门的客户端/服务端、可能被防火墙拦截。

4.2.3 Dubbo协议与HTTP协议

Dubbo 协议 vs HTTP 协议:从 OSI 或 TCP/IP 网络模型 的 网络分层角度看,Dubbo协议 和 HTTP协议 都工作在【应用层】,它们都位于【传输层(如TCP)之上】,为用户进程提供具体的通信服务,但设计目标和用途不同。

📌 网络分层视角(OSI 或 TCP/IP)

协议 所属层级 依赖传输层 是否标准协议
HTTP 应用层 TCP(或 QUIC) ✅ IETF 标准
Dubbo 协议 应用层 TCP(通过 Netty) ❌ 阿里巴巴私有协议

💡 补充:Dubbo框架 也 支持 HTTP 协议(通过 protocol="rest"),但这只是它的一种可选传输方式,并非默认。

// Dubbo协议报文结构示例(简化版)
+-------------------+-------------------+-------------------+-------------------+
| 魔术字 (2字节)    | 标志位 (1字节)     | 状态 (1字节)       | 消息ID (8字节)    |
+-------------------+-------------------+-------------------+-------------------+
| 数据长度 (4字节)  | 数据内容 (变长)    |                    |                   |
+-------------------+-------------------+-------------------+-------------------+

// 相比之下,HTTP协议的文本格式:
POST /com.example.UserService HTTP/1.1
Host: localhost:20880
Content-Type: application/json
Content-Length: 48

{"method":"getUser","params":[123],"id":1}

🎯一句话升华

HTTP 是“通用语言”,Dubbo 协议是 “行业黑话” ——
前者人人能听懂(适合对外 API),后者高效精准(适合内部微服务)。


4.2.4 Spring Cloud 整合 Dubbo 实现内外通信分离🧠

这里暂不讨论具体的实现方案。

基于上面的学习,就有了【Spring Cloud 整合 Dubbo 来实现 内外通信分离方案】,这样就可以让我们的 应用 同时拥有两种服务暴露方式:

  • 对内(微服务之间):使用 高性能 RPC(如Dubbo, gRPC)调用。
  • 对外(前端/外部系统):通过 Spring MVC 的 @RestController 提供 RESTful HTTP API,这个 Controller 内部可以调用 Dubbo RPC 服务来完成业务逻辑。

4.3 不同场景下的协议栈对比

4.3.1 技术栈对比

组件 应用层协议 传输层 主要特点
Spring Cloud服务调用 HTTP/REST TCP Socket 文本协议,易调试,有头部开销
MySQL数据库 MySQL私有协议 TCP Socket 二进制协议,高性能,专为DB优化
Redis RESP协议 TCP Socket 简单文本协议,高效
Dubbo RPC Dubbo协议 TCP Socket 二进制RPC协议,头部小
gRPC HTTP/2 + Protocol Buffers TCP Socket 二进制,流式,多语言支持

所有网络通信 最终都是通过 操作系统 提供的 Socket API 实现的


4.3.2 代码示例对比

// 场景1:HTTP调用(Spring Cloud)
@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // 底层:HTTP over TCP over Socket
        return userService.findById(id);
    }
}

// 场景2:数据库查询
@Repository
public class UserRepository {
    public User findById(Long id) {
        // 底层:MySQL协议 over TCP over Socket
        String sql = "SELECT * FROM users WHERE id = ?";
        // JDBC驱动会将此转换为MySQL协议包
        return jdbcTemplate.queryForObject(sql, User.class, id);
    }
}

// 场景3:直接Socket编程
public class RawSocketExample {
    public void connectToDatabase() {
        // 直接使用Socket连接MySQL(不推荐,需要自己实现MySQL协议)
        Socket socket = new Socket("localhost", 3306);
        // 需要手动实现MySQL协议握手、认证、查询...
        // 非常复杂!
    }
}

4.3.3 实际开发中的抽象层次

// 开发者看到的是:
// 1. Spring MVC注解(HTTP层)
// 2. JDBC/JPA接口(数据库层)

// 框架处理的是:
// 1. HTTP请求/响应编解码
// 2. SQL到MySQL协议的转换
// 3. 连接池管理

// 操作系统处理的是:
// 1. TCP连接建立/维护
// 2. Socket缓冲区管理
// 3. 网络包路由

// 示例:完整的调用链
@GetMapping("/user/{id}")
public UserDTO getUser(@PathVariable Long id) {
    // 1. HTTP请求到达,Tomcat解析HTTP
    // 2. Spring MVC路由到本方法
    User user = userRepository.findById(id); // 3. 调用JPA
    // 4. JPA/Hibernate生成SQL
    // 5. JDBC驱动转换为MySQL协议
    // 6. 通过Socket发送到数据库
    // 7. 数据库返回结果,反向处理
    return convertToDTO(user);
}

在微服务架构中,我们通常选择 最适合场景 的协议,而不是 一刀切 使用HTTP协议。这也是为什么即使有了HTTP,还需要gRPC、Dubbo、MQTT等各种协议的原因

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐