带你轻松学习计算机网络
本文系统介绍了计算机网络的核心协议与技术架构。首先详细解析了OSI七层模型和TCP/IP四层模型的各层功能与协议实现。随后完整梳理了HTTP请求的全流程,包括DNS解析、协议栈封装、TCP连接、IP路由等关键环节。重点对比了HTTP各版本特性,深入分析了HTTPS安全机制、WebSocket实时通信原理,以及RPC与HTTP的异同。在传输层部分,详解TCP协议的三次握手/四次挥手、可靠传输机制、拥
本文使用到的图片均来自网络
目录
(五)HTTP/1.1 vs HTTP/2 vs HTTP/3
(七)半连接队列(SYN队列)与全连接队列(Accept队列)
(八)TCP Keepalive 与 HTTP Keep-Alive 的区别
一、网络模型
(一)OSI 七层模型

七层模型是理解网络功能划分的经典抽象,是从最底层物理线路到最顶层应用协议。
- 应用层
- 为最终用户或应用程序提供网络服务与语义,定义应用级消息格式与交互语义。
- 典型协议:HTTP/1.1、HTTP/2、DNS、SSH、SNMP、DHCP、WebSocket。
- 表示层
- 负责数据的表示、编码、转换、压缩与加密等,使不同系统间能理解彼此的数据表示。
- 常见行为:字符集转化、二进制编解码、序列化和反序列化、TLS / SSL、gzip压缩。
- 会话层
- 管理进程间会话,提供会话控制与同步机制。
- 常见行为:建立长连接、心跳检测、重试策略、会话恢复。
- 传输层
- 在端点之间提供可靠或不可靠的数据传输,负责流量控制、差错检测、顺序保证与端口复用。
- 典型协议:TCP、UDP
- 网络层
- 实现逻辑寻址与路由选择,把数据从源主机送到目的主机。
- 数据链路层
- 把网络层包封装成帧在相邻节点间传输。
- 物理层
- 将数字信息转化为电信号进行传输。
(二)TCP/IP 四层模型

- 应用层
- 本模型的应用层的职责囊括了OSI模型的应用层、表示层与会话层。也就是说,本模型的应用层不仅提供了网络服务的协议集合、定义请求响应语义,还负责数据的渲染与处理以及会话的管理。
- 因为以上的职责其实都属于用户态,所以本模型的分类更为合理一些。
- 传输层
- 本层与OSI模型的传输层一致,都是提供TCP、UDP的传输网络服务,不负责数据的实际传输。
- 网络层
- 本层也与OSI模型一致,主要是负责IP的管理、路由转发、逻辑寻址与分片。
- 网络接口层
- 合并了OSI的数据链路层与物理层,将帧包装与数电信号转化封装到同一层。
二、客户端发起一次 HTTP 请求的全流程
(一)HTTP
此处是应用层,首先会对请求的URL进行解析,构造HTTP请求信息。
假设请求URL如下:
http://www.yilena.com/users/2025
http:代表使用的协议是HTTP,www.yilena.com是web服务器的域名,/users/2025是核心请求路径。
而HTTP请求信息分为了请求头、请求行、空白行与请求体,上面的URL解析后的请求信息如下:
GET HTTP/1.1
Accept: */*
(二)DNS
解析了请求信息后,就应该把这个消息发给对应的web服务器。不过我们没有服务器的IP地址,只有域名www.yilena.com。
所以在这里,我们需要DNS查询域名对应的IP。
域名的等级是从右往左递减的,而且URL中的域名是省略了根域名‘.’的,所以实际的域名应该是www.yilena.com.。
因此域名的层次也划分了DNS服务器的层级:
- 根DNS服务器(.)
- 顶级域DNS服务器(.com)
- 权威DNS服务器(yilena.com)
这三个层级实际形成了一个树形结构:

因此在DNS查询时,应用层会先发起一个DNS查询请求到本地DNS服务器当中,查找该服务器的缓存表中是否有对应的域名映射存储缓存,如果没有的话本地DNS服务器会发送请求到根域DNS服务器,根域DNS服务器会响应.com的顶级域DNS服务器的IP,本地DNS服务器再发送请求到顶级域DNS服务器,顶级域DNS服务器则会响应权威DNS服务器的IP,本地DNS服务器最后发送请求到权威DNS服务器,这个时候终于获取到了域名对应的IP地址的响应结果了,本地DNS服务器再把这个IP响应给应用层。
每次查询域名IP都这么麻烦吗?
聪明的你肯定想到了多级缓存。
应用层会先查询本地缓存,然后是OS缓存,再是host文件,最后才会去请求本地DNS服务器。
(三)协议栈
获取到IP之后,应用层就会把HTTP的传输工作交给OS的协议栈,注意此时我们来到了传输层。
协议栈其实是对传输信息的一个封装,需要加入TCP/UDP头、IP头、MAC头等等。
(四)TCP
协议栈解析后发现这是一次HTTP/1.1请求,而HTTP/1.1使用的是TCP协议,所以协议栈会先与服务器三次握手建立TCP连接。
随后TCP就会生成报文:

报文的每隔字段的详细会在后文进行讲解。
然后如果当前HTTP请求消息长度超过了MSS的长度,TCP就会把HTTP的请求信息以MSS为单位进行分片,放入单独的网络包当中。
最后TCP会把报文组装成TCP头放入协议栈当中,用于会话的管理以及其他相关操作。
(五)IP
这里来到了网络层,网络层会将源地址IP、目标地址IP以及其他信息封装成报文头部放入协议栈当中,用于目标方以太网内的传输:

假如客户端有多个网卡,那么该选择哪块网卡来发送网络包呢?
此时则需要根据本地路由表,用目标地址IP的二进制和每个网卡IP对应的子网掩码的二进制进行与运算,如果运算结果为网卡IP则代表匹配成功,如果失败则继续匹配下一个网卡IP。

我们可以看到第三条目的IP和子网掩码都是0.0.0.0,这里其实是兜底的默认网关,如果所有网卡IP都不匹配,那么则会采用Gateway的IP,也就是路由器的IP地址,后续会把包转发给路由器。
(六)MAC
现在来到了网络接口层,这里会将发送方MAC地址与目标放MAC地址封装成MAC头放入协议栈中,用于以太网之间的传输。
不过发送方的MAC地址可以直接读取本地的ROM获取,那么目标方的该如何获取?
这里我们使用ARP协议进行以太网间的广播,用之前解析的域名IP地址去询问其所属的设备MAC地址,获取后会写入本地缓存,短时间内相同域名的请求就无需广播了。
(七)网卡
网卡会给网络包的开头加上报头和起始帧分界符,在结尾加上帧校验序列,用于接收方判断网络包起始处与检测数据完整性。
随后会将数字信号转化为电信号,通过网线将网络包发送出去。
(八)交换机
接下来网络包抵达了交换机,交换机会先把电信号转化为数字信号,通过帧校验序列检测完整性。
交换机是基于以太网设计的,每个端口都不具有MAC地址。
随后交换机会通过MAC头获取目标的MAC地址,然后通过本地MAC地址表查找是否有对应的端口号映射,不然交换机无法得知该网络包应该发给当前MAC地址设备的哪个端口。
如果本地MAC地址表中不存在当前MAC地址的端口映射缓存,那么将该包转发到该设备的所有端口上,不符合的端口会自动忽略这个包,而符合的端口则会返回一个响应包,交换机则会将这个端口写入MAC地址表当中。
找到对应的端口号后,交换机会将包从数字信号转化为电信号发送出去。
(九)路由器
如果网络包抵达了路由器,路由器也会先把电信号转化为数字信号,通过帧校验序列检测完整性。
路由器是基于IP设计的,每个端口都具有MAC和IP地址。
然后获取MAC地址,检测其目标方的MAC地址是否与自身的MAC地址相同,不同则代表这个包不是发给自己的,则直接丢弃。
到这里MAC头的任务就完成了,路由器会丢弃掉网络包的MAC头。
随后取出IP头中的域名IP地址,通过与路由表的每个条目的子网掩码进行与运算匹配。
匹配成功后则会进入转发操作,首先会检测刚才匹配的条目中的Gateway是否存在IP地址,如果不存在则证明当前网络包还没到目标点,还需要进一步转发,反之则证明抵达终点。
之后路由器也会通过ARP协议进行广播获取目标MAC地址,封装成MAC头装载到网络包前面。
最后转化为电信号进行转发。
之后或许直接到达终点,或许还需要通过好几个路由器的层层转发,但是发送方IP和目标方IP一直不变,变的只有MAC地址。
这也更加凸显了MAC地址是用于以太网内两个设备之间传输的职责。
(十)服务器响应
网络包终于抵达了目标服务器,服务器会进行上面相反的操作。
先解析MAC头检测MAC地址是否与自身的一致。
然后再解析IP头检测IP地址是否一致,若一致再获取对应的协议。
再解析TCP头检测序列号是否一致,若一致再返回ACK。
最后转发给服务器的HTTP进程,HTTP进程获取解析后发现是要通过用户ID获取用户的详细信息,则会将用户的详细信息响应给客户端。
当然,这个响应也是要重复客户端请求的过程,需要层层包装。
在最后,客户端如果要终止了,则会发起四次挥手,终止TCP的连接。
三、HTTP
(一)基本概念
HTTP 本质上是一个应用层的请求/响应协议:客户端向服务器发送请求,请求包含方法、URL、请求头(Headers)和可选的请求体(Body);服务器处理后返回状态码、响应头与响应体。
其全称是超文本传输协议,简单来说就是在计算机中两端之间传输文字、视频、音频、图片等所谓超文本数据的规范。
需要注意的是HTTP是无状态的,这意味着每次请求都是独立的,服务器默认不会保留客户端状态。状态通常由客户端进行保存,然后通过Cookie、Token、Session等方式实现服务端的鉴权。
(二)状态码体系
请见下面这篇博客:八、RestFul
(三)HTTP 缓存
针对重复请求的资源,HTTP自身带有缓存机制,可以节省多余的网络开销、减少延迟并分担服务器的压力。
而HTTP提供了两种缓存方式:
1. 强制缓存
这种就是很常见的缓存方式,当响应头携带了Cache-Control或者Expires字段时,浏览器会将请求到的数据和TTL缓存到客户端本地。
当再次发起相同的请求时,浏览器会先检查本地缓存是否过期,若没有则直接使用,反之则请求到服务端。
Expires的value只能是绝对时间,而且优先级不如Cache-Control,也就是说其会被Cache-Control给覆盖。
而Cache-Control则提供了更为精细的value选择:
- public:资源可以被任何缓存存储。
- private:资源只能被用户的浏览器缓存,不能由共享缓存存储。
- no-cache:请求前必须向服务器验证资源。
- no-store:不缓存任何数据。
- max-age:缓存的最大存活时间,单位为秒,也就是相对时间。
这种缓存方式实现起来最为简单方便,但是同时也会有缓存数据不一致的风险,如果服务端的数据发生变更,客户端无法进行感知,所以客户端仍旧可能返回无效的旧数据,也就是脏读。
2. 协商缓存
协商请求又分为两种方式:
(1)基于时间
服务端响应请求时会检查资源的最后修改时间,然后将其放入响应头的Last-Modified字段中响应给客户端。
客户端会在下一次请求时,将上面的Last-Modified中的时间值放入请求头中的If-Modified-Since字段中请求给服务端。
服务端收到请求后会比对当前该资源的最后修改时间,若比If-Modified-Since字段中的时间要晚,就证明该资源已经被修改过了,需要重新响应新资源;反之则响应304状态码通知客户端使用本地缓存。
这个方式的精度比较低,因为精度仅精确到秒,而且时间存在被篡改的风险,除此之外,部分场景下服务端无法获取到资源的最后修改时间。
(2)基于唯一标识
大体流程与上面一致,不同的只是字段:服务端响应的ETag字段是响应资源的哈希值;客户端请求的字段名是If-None-Match。
哈希值比对的精度比时间比对要高得多,而且其不依赖于时间。
不过相对的哈希计算会带来额外的开销,对于频繁变更的资源来说可能带来的性能开销会比较明显,此时使用基于时间的协商缓存或者强制缓存效果会更好。
协商缓存相较于强制缓存需要额外一次网络IO,那为什么不直接舍弃HTTP缓存,改为直接使用服务端中的本地缓存或者中间件的缓存?
确实,两次网络IO会带来额外的网络延迟,特别是在低带宽或者高延迟的网络环境中,协商缓存的开销会非常大,但是它在许多其他场景中依然是非常有价值的。
如果使用强制缓存就难以保障缓存一致性,如果不使用HTTP缓存,那么在响应资源较大(例如视频)的时候,每次传输都会占用带宽,影响其他请求的响应。
除此之外,服务端在获取资源的时候可能会进行数据库、消息队列或者中间件的交互,而这些交互通常需要更多的网络IO,而且还会占用数据库线程池资源。
所以综合权衡来看,协商缓存的额外一次网络IO能带来的收益会更大。
不过针对于资源更新频次极小并且注重性能的业务场景下,更推荐使用强制缓存。
在需要控制带宽、保障缓存一致性且资源更新频次不是过于频繁的业务场景下推荐使用协商缓存。
(四)HTTPS
HTTP的信息是明文传输,所以很不安全,传输内容容易被窃取、篡改,甚至用户访问的网站都可能使被冒充的盗版网站。
因此HTTPS应运而生,解决了上述三个问题。

可以看到HTTPS在原本的HTTP层和TCP层中间又加入了一层SSL/TLS层。
这一层会对传输内容进行加密,还会生成唯一标识防止篡改,然后对于盗版网站问题,则采用数字证书校验的方式来解决。
TLS的握手流程:
TLS握手主要用于密钥交换和认证,然后主流的有两种方式:
1. RSA握手
这是比较传统的公钥加密算法。使用它时,客户端会先生成一个共享密钥,然后使用服务器的公钥进行加密并发送给服务器。服务器收到加密数据后用私钥解密后得到共享密钥,然后基于该密钥生成会话密钥。
但是这个算法不支持前向保密,也就是说一旦私钥泄露,那么之前的所有加密对话都会被解密。
除此之外,RSA还需要对大数进行幂运算,计算负担较重,效率较低,尤其在高并发场景下。
2. ECDHE
这时基于椭圆曲线 Diffie-Hellman 协议的一种实现。与RSA不同,该算法会使用临时的密钥对进行密钥交换,每次握手时客户端和服务器都会生成各自的临时密钥对,并使用这些临时密钥交换数据,最终计算出共享的密钥。
这个算法相比RSA,首先支持前向保密;其次算法效率更快,计算负担较轻;最后是这个算法更加安全,因为其可以使用比RSA更短的密钥就能提供相同的安全性,因此加密和解密速度更快。
详细流程如下:
首先服务端把自己的公钥注册到数字证书认证机构CA,CA会用自己的私钥将服务端的公钥数字签名并颁发证书给服务端。
然后当客户端发起请求时,会先向服务端请求数字证书和公钥,获取响应结果后会使用CA的公钥进行服务端数字证书的校验,以防访问到了盗版网站。
客户端如何获取CA的公钥?
一般浏览器或者操作系统会内置CA的公钥。
当数字证书为真时,客户端会使用刚才获取到的服务端的公钥,对HTTP报文进行加密后发送真正的请求到服务端。
服务端接收到请求后会使用私钥进行解密,进行业务处理并私钥加密后响应给客户端。
为什么要使用一个公钥一个私钥?
客户端发送请求时是公钥加密私钥解密,这是为了保证内容传输安全,因为请求可能会携带个人信息。被公钥加密的内容只能使用私钥进行解密,而私钥由服务端进行保管,所以即使请求被中间人拦截了也无法从中截取用户隐私。
服务端响应数据时是私钥加密公钥解密,这是为了证明响应方的身份,因为可能会有中间人伪造服务端向用户响应假数据。公钥只能解密被私钥加密的内容,也就是说如果响应数据无法被正常使用公钥解密,那么就能证明这是伪造响应数据。
就此,一次请求发送完成。
如果HTTPS请求被假基站转发到了中间人服务端了该怎么办?
当客户端发起请求向服务端请求数字证书和公钥的时候,需要进行TLS握手。但是这个请求被假基站劫持,并转发到一个中间人服务端,而中间人服务端则会将这个请求转发到真正的服务端,并将自身作为客户端与其进行TLS握手,最终拿到公钥。
与此同时,中间人服务端还会将自身作为客户端,将自己的公钥和数字证书发给客户端验证,若验证通过则也TLS握手完成。
就此,中间人服务端既是服务端又是客户端,既可以让发起的请求通过真正服务端的鉴权,又可以获取服务端的响应数据并解密。也就是说,真正的客户端的请求数据和真正的服务端的响应数据都能够被中间人服务端解析,这是极其危险的。
不过细心的同学可能发现了,在上述流程中,有一步是无法完成的,那就是中间人服务端与客户端进行TLS握手时给客户端发送数字证书校验这一环节,客户端用CA的公钥是无法校验该证书的,也就是说该证书并不合法,客户端并不会同意进行TLS握手。
但是当下许多浏览器并不会立即拒绝建立连接,而是给用户提供选择的权利:
如果用户仍旧选择继续访问,就代表客户端无视了这一步的风险,导致TLS握手成功,此时就可能被中间人服务端窃听数据。
不过这也不怪HTTPS,毕竟是用户自主选择的,HTTPS已经识别出了风险行为了。
(五)HTTP/1.1 vs HTTP/2 vs HTTP/3
1. HTTP/1.0
传统的HTTP协议,使用单一TCP连接进行请求和响应,这种短连接造成的性能开销非常庞大,尤其在高并发场景下。
2. HTTP/1.1
为了解决HTTP/1.0短连接的性能开销,HTTP/1.1使用了长连接(Keep-Alive)的方式,允许在同一个TCP连接上发送多个请求和响应,但是这在大量短时请求场景下可能导致连接池资源紧张,需要通过合适的连接策略来优化。
除此之外,HTTP/1.1还支持管线化传输,在同一个管道内,只要第一个请求发送后,无需等待响应就可以发送第二个请求。
不过由于响应顺序是与请求顺序一一对应,所以如果第一个响应结果没有返回,第二个响应也会阻塞,这就是所谓的队头阻塞问题。
除此之外,请求头响应头无法压缩,冗长的头部不仅会增加网络延迟,还会再重复请求常见下造成资源浪费。
最后,HTTP/1.1不支持服务端进行请求,只能被动响应客户端的请求。
3. HTTP/2
针对 HTTP/1.1 的缺陷,HTTP/2 进行了多项重要改进:
首先,针对冗长且重复的请求头,HTTP/2 引入了 HPACK 压缩算法:
客户端与服务端会共同维护一张头部表,用以存储已经出现过的字段。当再次发送相同的头部时,只需引用其索引号即可,而无需重复传输整个字段,从而显著减少冗余数据传输、提升带宽利用率。
其次,HTTP/2 将原本的纯文本报文改为二进制分帧。
所有数据都被拆分为最小的传输单元——帧(Frame),主要包括头部帧(HEADERS Frame) 与数据帧(DATA Frame)。二进制格式更易解析与复用,也为后续的并发传输提供了基础。
最后,为了解决 HTTP/1.1 的队头阻塞问题,HTTP/2 引入了流(Stream)的概念:
一个TCP连接可以承载多个Stream,每个Stream对应一个完整的HTTP 请求响应过程;每个 Stream又由若干Frame组成。
不同请求会分配到不同的Stream中,并以帧为单位交错发送。服务端根据Stream ID将这些帧重新组装为完整的请求并进行处理与响应。
这种机制实现了多路复用:多个 HTTP 请求可以在同一连接上并行传输,而不会因为先发的请求尚未完成而阻塞后续请求。
此外,HTTP/2 还引入了服务器推送机制。
协议允许服务器在客户端未明确请求的情况下,主动创建Stream并发送资源,从而提前推送可能会被使用的内容,进一步降低延迟。
尽管 HTTP/2 在性能上有显著提升,但仍存在一个关键缺陷:它依然运行在单一的TCP连接之上。
一旦该连接中任意一个分片丢失,TCP的顺序可靠传输机制会导致后续所有数据都必须等待该包重传,这种现象依然会造成传输层级的队头阻塞。
因此,虽然 HTTP/2 消除了应用层的队头阻塞,但并未彻底解决底层的传输瓶颈。
4. HTTP/3
由于HTTP/2的队头阻塞问题出在TCP的可靠性上,那么HTTP/3就干脆抛弃了这个可靠性,改为不可靠的UDP协议,UDP既不管传输顺序也不管丢包,所以可以避免HTTP/2的丢包问题。
但是这样传输就不可靠了,所以HTTP/3并没有使用裸UDP,而是采用了基于UDP的QUIC协议,可以实现类似TCP的可靠性传输。
首先针对丢包现象,QUIC只会阻塞丢包的Stream,而不影响其他Stream,这就代表只有丢包的请求会受影响,其他请求仍可正常传输。
其次,HTTP/1和HTTP/2都会先进行TCP握手再TLS握手,但是由于HTTP/3的QUIC协议中包含了TLS,所以可以减少握手延迟。
最后,QUIC协议没有使用四元组的方式来管理连接,而是通过ID来标记连接的两个端点,因此当设备切换网络时,只要保留连接ID与TLS密钥等上下文信息,就可以实现无缝切换网络,达到了连接迁移的效果。

(六)WebSocket
1. 轮询
传统的实时通信实现基于轮询实现,客户端每隔一段时间就会向服务端发送请求,服务端会响应最新的数据。
但是这个方法首先实时性会很差,如果数据发生了变更,客户端得等待请求发送以及接收响应的时间后才能接收到这次变更。
除此之外,若数据长时间不变更,则客户端一致发送的都是无效请求,浪费宽带还占用服务器的资源,非常划不来。
2. 长轮询
为了减少频繁的无效请求,长轮询在客户端发送一次请求过后,服务端会一致保持连接,直到数据发生变更后才会响应数据给客户端并关闭连接。
这虽然比普通轮询效率更高,资源消耗也大大减少,但是每次新请求依旧需要重新建立连接。
3. WebSocket
WebSocket是一种基于TCP的双向通信协议,它允许客户端和服务器在单一的连接上进行全双工通信,而无需像HTTP那样每次请求都建立连接。
首先其会通过HTTP协议的Upgrade机制将当前连接从HTTP协议升级到WebSocket协议,之后会在客户端和服务端之间建立一条可以长期保持的连接,让客户端和服务端可以互相实时传输数据。
足以可见WebSocket在实时性、资源占用等方面上都极具优势。但是缺点就是长连接的资源占用问题,尤其在高并发场景下;其次还有管理连接的复杂度,需要处理连接断开及重连的问题。
那么轮询就没有应用场景了么?
不是的,轮询仅仅只是在实时通讯领域上远远不如WebSocket而已,但在一些实时性要求稍低且高并发场景下,轮询更加具有优势,最为典型的就是扫码登录:
客户端在收到服务端传来的唯一标识后会调用前端库生成二维码,随后就会每隔几秒发起一次请求到服务端,校验用户是否已经扫码;当然也有使用长轮询进行校验的。
使用普通轮询的话在用户扫完码后客户端会等待下一次轮询才会得到用户已经扫过码的状态,这时才会反馈到前端,显示二维码已经被扫,会有一定延迟;而长轮询就不会有这种延迟,可以很快地反馈到前端。
虽说普通轮询有延迟,不过回想一下,其实我们使用的部分扫码登录都是有这个延迟的,但是我们用户都是可以接受的不是吗?因为我们主观判断这种跨设备操作有些延迟很正常。
在这种延迟包容场景下,普通轮询的小开销和简易性的优势就发挥出来了。
所以在这个场景WebSocket就不适用了。
(七)RPC 与 gRPC
1. RPC
RPC是一种协议,允许客户端调用远程服务器上的函数,屏蔽网络细节。RPC的目标是让分布式系统中的各个组件之间像调用本地函数一样进行通信。
与HTTP协议相同,二者都基于TCP,都解决了TCP的粘包问题。
什么是粘包?
裸TCP传输只有二进制消息体,没有任何边界说明,所以接收方无法判断接收到的消息体是否完整,而且也可能出现解析时的语义错误问题。
那HTTP和RPC到底有什么差别呢?
在服务发现方面,HTTP通过的是DNS服务;RPC一般会用一些中间件来保存域名、IP以进行匹配,二者大差不差。
什么是服务发现?
就是在与服务端建立连接之前,需要知道服务端的IP以及端口号。而寻找的过程就称之为服务发现。
在底层连接方面,HTTP/1.1的Keep-Alive和HTTP/2的Stream,RPC的连接池,其实都是为了节省资源减少开销,而且如今大部分编程语言的网络库都会给HTTP加个连接池,所以这一点也大差不差。
最后在传输报文上,HTTP由于是客户端到服务端的传输规范,其报文会很冗余,因为其主要应用场景就是浏览器,所以很多设计都顾虑到了浏览器,显得冗余,部分字段即使我们并不需要,其HTTP报文也会携带并传输。而RPC的定制化程度更高,可以以更小的体积传输数据,而且也无需向HTTP一样顾虑浏览器行为,由此更为轻量,性能也更好。
HTTP和RPC无需争个高下,二者所注重的领域是有所不同的:
针对客户端-服务端场景,HTTP更加适配于浏览器的各种行为,而且当下更为主流的HTTP/2的Stream机制也带来了更好的性能。
针对微服务场景,HTTP的REST + JSON通常带来的序列化、性能开销更大,而且离开了浏览器,HTTP的很多行为就显得冗余;RPC则不同,以gRPC为例,其基于HTTP/2,采用了其Stream机制,在此基础上采用二进制协议压缩数据体积提高传输性能,而且也没有HTTP的冗余字段。
所以在对外接口暴露的场景下优先采用HTTP;在服务内部调用的场景下应该优先采用RPC。
2. gRPC
gRPC是Google开源的一个高性能、跨语言的RPC框架。它基于HTTP/2协议,使用 Protocol Buffers作为接口定义语言和消息序列化格式。gRPC支持多种通信模式,包括单次请求/响应、流式传输等。
- 高效性:protobuf序列化格式比JSON更紧凑,减少了带宽开销。
- 多路复用与并发:gRPC使用HTTP/2协议,支持请求多路复用,减少了延迟。
- 支持流式通信:支持双向流、单向流等多种数据流模式,适用于需要实时数据交换的场景。
- 跨语言支持:gRPC 支持多种编程语言,包括 Java、Go、C++、Python 等,适合微服务架构中的跨语言通信。
四、TCP
(一)基本概念
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接:必须一段对一段,无法一对多。
- 可靠的:TCP保证报文一定能够传输到接收端。
- 字节流:保障分片的包可以被完整接收。
先聊一下TCP报文中的各个字段吧:

- 序列号:建立TCP连接时计算机生成的随机数,通过SYN传给接收端,每发送一次数据就累加一次该次数据字节数的大小,由此来解决网络包乱序问题。
- 确认应答号:指接收端下一次期望收到的序列号,发送端接受到响应数据,解析拿到其中的确认应答号,就可以认为在这个应答号之前的序列号的网络包都正常发送了。由此来解决丢包问题,如果确认应答号比请求的序列号小,就证明应答号之后的序列号的网络包丢了,需要重发。
- ACK:为1时代表确认应答号有效。TCP规定处理最初建立连接时的SYN包以外其他包的ACK必须设置为1.
- RST:为1时代表TCP连接异常,需要强制断开连接。
- SYN:为1时代表希望建立连接,一般SYN包的序列号就是整个TCP连接的初始值。
- FIN:为1时代表希望断开连接,双端交换FIN包后断开连接。
(二)三次握手 / 四次挥手
1. 三次握手

- 客户端和服务端都处于CLOSE状态。
- 服务端启动,开始监听某个端口,变更为LISTEN状态。
- 第一次握手,客户端启动,发起第一个请求,也就是SYN包,该报文中会随机初始化序列号,同时SYN位置1,随后变更位SYN_SENT状态。
- 第一次握手如果丢失了,客户端会重新发送SYN包,最多3次,若还是失败就会断开连接。
- 第二次握手,服务端收到客户端的SYN包后,会随机初始化自己这端的序列号,SYN置1;随后将确认应答号填充为客户端的序列号+1,ACK置1,发送给客户端,随后变更为SYN_RCVD状态。
- 第二次握手如果丢失了,客户端会重新发送SYN包,最多1次,失败则断开连接;服务端则会重新发送SYN-ACK包最多2次,失败则断开连接。
- 第三次握手,客户端收到后,也需要返回一个ACK包给服务端,之后变更为ESRABLISHED状态。
- 第三次握手如果丢失了,服务端则会重新发送SYN-ACK包最多2次,失败则断开连接。
- 服务端收到后也变更为ESRABLISHED状态,流程结束。
发送SYN包代表己端具有发送能力,发送ACK包代表己端具有接收能力。服务端将两个包结合,节省了一次网络IO,所以三次网络IO三次握手下来,就可以确定双端都具有收发能力。
第三次握手可以携带数据,但是前两次不可以。因为第三次携带数据一旦抵达服务端就结束握手,还可以节省一次额外的网络IO,前两次还在握手中途,无法确认双端是否都有收发能力。
为什么握手只需要三次?
最为首要的原因就是三次是网络IO次数最少又能同时证明双端都具有收发能力,少一次都不行,因为总共需要发送四个包,其中只能有一端一次性发送两个包。
某些文章说同步双方序列号、防止历史连接也是因为握手只有三次的原因,我认为这种说法是不恰当的。
应该说三次握手只是证明双端都具有收发能力的最少次数,而同步双方序列号、防止历史连接应该是TCP对于握手的处理带来的好处。
针对双端序列号的同步,是由SYN包的序列号以及ACK包的确认应答号来保障的,双端都能初始化自己的序列号,而且都能确保自己的序列号被对方所接收,保障后续网络包都可以被对方按次序接收。
针对防止历史连接,这个也是与同步是相同的原因,假设客户端之前的旧SYN包的序列号是50,新SYN包是60,旧SYN包比新SYN包发送更早,被服务端所接收,服务端返回50 + 1的ACK包到客户端,客户端发现ACK包的确认应答号比自己的序列号要小,就证明此次服务端接收到的是旧包,就会发送FIN包给服务端请求断开连接,重新握手。
2. 四次挥手

- 第一次挥手,客户端发送FIN包,FIN置1,随后状态变更为FIN_WAIT_1。
- 第一次挥手如果丢失了,客户端会重传最多3次FIN包,失败则断开连接
- 第二次挥手,服务端收到客户端的FIN包,向客户端发送ACK包,随后更改为CLOSE_WAIT状态,开始清理数据。
- 第二次挥手如果丢失了,客户端会重传最多2次FIN包,失败则断开连接。
- 客户端收到ACK包,进入FIN_WAIT_2状态。
- 第三次挥手,服务端清理数据完毕,向客户端发送FIN包,随后变更为LAST_ACK状态。
- 第三次挥手如果丢失了,服务端会重传最多3次FIN包,失败则断开连接;客户端等待超时后自动断开连接。
- 第四次挥手,客户端收到FIN包,发送ACK包给服务端,之后进入TIME_WAIT状态。
- 第四次挥手如果丢失了,服务端会重传最多2次FIN包,失败则断开连接;客户端等待超时后自动断开连接。
- 服务端收到ACK包,进入CLOSE状态,服务端关闭连接。
- 2MSL时间过后,客户端自动进入CLOSE状态,客户端关闭连接。
2MSL是多久?
MSL是报文最大生存时间,2MSL那就是报文最大生存时间 * 2。
为什么需要TIME_WAIT状态?
确保最后一个ACK包如果丢失,服务端能重传FIN包并得到ACK包。
防止旧的重复报文在 2MSL时间内干扰新连接。
为什么是2MSL?
TIME_WAIT状态时间如果过长则会占用系统和端口资源,过短则无法保障上述两个需求,所以需要找到一个平衡点。
2个MSL刚好是请求一来一回的最大时间,客户端发送FIN包的最大传输时间是MSL,如果超过了就代表第四次挥手丢失,服务端会再次发送FIN包,而这次的最大传输时间也是MSL。所以如果2MSL都没有让第四次挥手成功,那么客户端只能自己自动关闭连接。
客户端的FIN包代表客户端不再发送数据,但还可以接收数据,服务端的ACK包表示自己接收到了FIN包,此时服务端可能还存在未处理或者未发送完的数据,等服务端处理完成后发送FIN包给客户端表示自己不再发送数据,客户端发送ACK包表示收到。
就是因为服务端需要处理剩余数据,所以不能合并FIN包和ACK包,导致挥手是四次。
那当没有剩余数据的时候不就可以合并了吗?
如果服务端开启了TCP延迟确认机制,那么当有剩余数据要发送时,会将其放入ACK包中一并发送,如果没有的话则会等待一段时间,若这段时间内没有收到该客户端的报文,则会将ACK包和FIN包合并发送,导致挥手变成了三次。
(三)重传机制
1. 超时重传
当数据包丢失或者没有收到ACK包的时候会触发超时重传,每次超时重传,都会将下一次超时的时间间隔设置为上一次的两倍,这样如果超时次数达到2-3次,就证明网络环境差或者其他难以解决的问题。
2. 快速重传
当接收方收到序列号不连续的数据时,会立刻发送重读的ACK包以通知发送方已经收到后续数据但是丢包了,当发送方连续收到3个ACK包则会触发快速重传立即重新传输一次。
(四)滑动窗口
传统发送一个包必须等待ACK后才能发送下一个包,这样效率太慢了,所以TCP引入了滑动窗口,可以一次性发送多个包,然后并行等待ACK。
发送窗口表示发送端允许同时未收到ACK的数据量,当窗口满了后不允许发送其他包,当其中一个包收到ACK后窗口则滑动向前。
接收窗口表示接收方当前还能接收的数据量,因为发送端发送的请求实际是存储在接收端的缓冲区中,这个缓冲区是有限的,如果满了,即使发送窗口还没满,也不能再发送请求。
(五)拥塞控制
1. 核心思想
TCP 的目标是在不引发网络拥塞的情况下尽可能高效地利用带宽。
拥塞控制通过调整发送速率来平衡效率与稳定性,其核心变量是拥塞窗口。
其中发送端的发送速率约等于min(cwnd, rwnd)。
cwnd是由发送方动态调整的参数,rwnd是接收方的流量窗口还能接受的数据大小。
2. 慢启动
慢启动并不是慢,而是指初始阶段从小窗口开始指数级增长,以快速探测网络容量。
初始 cwnd 通常为 10 MSS,每收到一个ACK,cwnd就增加1MSS。
知道cwnd达到慢启动阈值或者发生丢包,才会进入拥塞避免阶段。
3. 拥塞避免
此时的增长速度由指数变为线性,每收到一个ACK,cwnd就增加约 1 / cwnd 个MSS。
这依旧是让增长变缓,避免网络因过快膨胀而崩溃。
4. 快速恢复
当发送端一开始就连续收到三个重复ACK,代表原本发给接收方的包丢失了,也就代表此时并不是发送方刚启动,很有可能是丢包后断连重建。此时为了快速恢复原先状态,TCP会让慢启动阈值变为原先的一半,并且增长速度也大大增加。
(六)流量控制
流量控制关注的是接收端的处理能力。它确保发送端不会发得太快,以至于接收方缓存溢出。
而其核心实现其实就是上文所说的接收端的缓冲区。
如果缓冲区几乎要满了,那么接收端就会响应一个较小的rwnd,反之则是一个较大的rwnd。
当rwnd为0时。发送端会停止发送数据,并周期性发送探测包来检测接收方是否恢复了接收数据的能力。
(七)半连接队列(SYN队列)与全连接队列(Accept队列)
当第一次握手,服务端接收到客户端的SYN包时,现会把这次连接请求放入半连接队列中,目的是记录对方IP、端口、初始序列号等信息。
当第三次握手,服务端接收到客户端的ACK包时,此时三次握手完成,可以确认连接,服务端就会把连接请求从半连接队列移动到全连接队列,等待应用层调用accept()函数建立连接。
也就是说,半连接队列是握手中间态的一个存储容器,用于等待后续的ACK包;而全连接队列则是预防应用层短时间内无法处理大量连接请求,从而设置的一个缓冲区。
如果有人恶意发起网络攻击,伪造多个IP不断发送SYN包,服务端因为无法确认真伪,就会一股脑地将这些连接请求全部放入半连接队列中,直至队列满为止,一旦队列满了就无法接收真正的连接请求了。
这个时候可以启用SYN Cookies机制,此时半连接队列失效,服务端会把原先需要记录的信息编码进服务端ACK包中的序列号,当第三次握手客户端返回ACK包时,服务端就可以解析其确认应答号获取信息进行验证,验证通过后就会放入全连接队列。
(八)TCP Keepalive 与 HTTP Keep-Alive 的区别
TCP的Keepalive是当双端长时间没有数据交互,那么一端就会发送探测包给对端,如果对端响应了就会重置本连接的存活时间,如果没有响应且已经过了存活时间,那么就会自动断开连接。
而HTTP的Keep-Alive就是一个长连接,让同一个TCP连接可以承载多个HTTP请求-响应。
所以这两个只是名称上相似,实际内容大相径庭。
(九)UDP
UDP是一种无连接、不可靠、面向报文的传输层协议。
它不保证顺序、不重传、不确认,但延迟极低、开销小。
所以UDP经常被用于实时性要求高、丢包包容的场景,就像音乐、视频、游戏等等。当我们游戏、视频卡了,就可能是因为网络环境问题导致丢包,而这些场景也通常不会使用裸UDP,而是类似QUIC会有自己的一套可靠性保障机制,所以丢包后基本会进行重传,重传的过程就是网络延迟。
五、IP
(一)基本概念
IP是网络层的核心协议,其主要职责是为主机之间提供一种无连接、不可靠、尽力而为的数据传输服务。
无连接代表发送数据不建立连接,其次不保证包一定送达,但是会尽量把包送出去。
IP的主要任务其实就是寻址和路由,针对传输通常是其上层的TCP、UDP等协议负责。
(二)IPv4 vs IPv6
| 对比项 | IPv4 | IPv6 |
|---|---|---|
| 地址长度 | 32 位(4 字节) | 128 位(16 字节) |
| 地址数量 | 约 43 亿个 | 理论上 3.4×10³⁸ |
| 地址表示 | 点分十进制,如 192.168.1.1 | 十六进制,如 2001:0db8::1 |
| 首部长度 | 20~60 字节可变 | 固定 40 字节 |
| 校验和 | 有(Header Checksum) | 无(靠链路层和上层校验) |
| 分片 | 由中间路由器处理 | 仅发送端可分片 |
| 广播 | 支持 | 不支持(改用多播) |
| 安全性 | 可选 IPsec | 内建 IPsec |
| 配置方式 | 静态 / DHCP |
自动配置(SLAAC)或 DHCPv6 |
IPv6 的设计目标:
- 解决地址枯竭
- 提升转发效率
- 改善移动性
- 内建安全支持
- 通过NDP机制取代ARP
(三)NAT
原本的IPv4的地址有限,可分配的公网地址很少,如果让每台主机都拥有一个公网IPv4地址成本太高,很容易让IPv4地址枯竭。
为了解决这个问题,NAT应运而生,其目的是让家庭/企业等这些有一定共享性质的环境下的主机都共享同一个公网IP,每台主机各自占用这个IP的不同端口。然后在这个公网内的主机使用私有地址,一般是192.168.x.x,由此让公网内主机可以互相路由通信。
那么NAT是如何做到的呢,NAT在IP层维护了一个映射表,将私有地址与公网地址的端口映射保存起来,并在数据包出站和入站的时候进行改写,这样就实现了多个主机共享同一个公网IP。
(四)ARP、DHCP、ICMP、IGMP
1. ARP
我们上文提到过,获取接收方的MAC地址时需要用到ARP协议。
其实也很简单,发送方会通过广播发送ARP请求,包中存放了接收方的IP地址,同链路的设备接收到ARP请求后会解析其IP地址进行比对,若比对成功则会把自己的MAC地址放入ARP响应包返回给接收方。
2. DHCP
DHCP 用于自动给主机分配 IP 地址、子网掩码、默认网关、DNS、以及其他网络参数。目标是实现零配置接入与集中管理地址租用。
一般流程分为四步:
- 客户端广播查找DHCP服务器
- DHCP服务器返回可用的IP地址以及对应的子网掩码、网关、DNS服务器以及IP地址租用期
- 客户端选择其中一个IP地址并使用这个IP地址通知DHCP服务器请求租用
- DHCP服务器确认并发放租约,返回ACK;但如果期间这个IP被其他客户端使用或者其他情况时服务器不能同意租用,会返回一个NACK。
3. ICMP
ICMP 属于IP层的控制/诊断协议,用于反馈错误与提供网络状态信息。ICMP报文源自IP,并由路由器或终端主机生成以便告知发送方问题或用于诊断。
当IP通信中某个IP包未能送达,ICMP会通知发送端具体的原因。

4. IGMP
IGMP 是IPv4用于主机与本地路由器/交换机之间管理多播组成员关系的协议。
换句话说,IGMP 的任务是告诉局域网内的路由器/交换机:哪些主机想接收哪个多播地址。
其工作流程也很简单:
- 路由器周期性发送 IGMP 查询
- 感兴趣的主机在收到查询后会发送 IGMP 报告,向路由器声明它们要加入的多播组。
- 路由器根据收到的报告在其多播转发表中记录哪些接口上有该组的成员,从而后续仅把该组流量转发到有订阅主机的接口。
- 若主机发送 Leave 报文或长时间不再报告,路由器可发组特定查询确认,确认无人订阅后停止转发该组。
(五)MTU、Path MTU Discovery
1. MTU
MTU是某一链路层单次可承载的最大IP包总长度(不含链路层头),通常以字节为单位。也就是说是IP报文的最大字节数。
如果当一个IP包的字节长度超过了MTU,IP层就会进行分片,每个片都有IP头,除了最后一个片外,每个片的数据长度必须是8字节的倍数,这是分片的偏移量。
既然TCP有分段功能,为什么IP还需要进行分片?
首先是职责不同:
- TCP分段:发送端把应用字节流切成合适的TCP段,目的是可靠传输与流量控制,更注重可靠性。
- IP分片:当 IP 包大于某链路的MTU时,路由器把包拆成若干IP片段以便通过链路,目的是适配链路限制并保持可达性,更注重兼容性。
其次不是所有的传输层都使用的是TCP协议,当传输层失去分片功能时,就只能让IP层来分割大IP报文了。
但是分片就意味着只有这些片全部达到都才能被正确解析,但凡丢包都需要重传,会有额外开销;再加上与TCP的分段不同,IP的分片一旦丢失,整个包就无效需要重传了,所以在实际场景下我们应该尽量避免IP分片。
2. Path MTU Discovery
Path MTU Discovery的目标,是在通信开始时动态探测出通信路径上最小的MTU,然后让发送端在传输层或网络层根据这个值控制报文大小,避免中间路由器执行 IP 分片。
它的工作机制是:发送端先假设一个较大的 MTU 发送数据包,并在 IP 头中设置DF标志位。如果途中某个路由器发现该包超过本链路 MTU,就会丢弃该包并返回一个 ICMP 报文,告诉发送端可接受的最大 MTU。发送端据此调整报文长度,直到不再收到此类 ICMP 消息,此时的包长即为路径上的最小 MTU。
这种机制能有效避免中间节点分片,提高传输性能与稳定性。不过,若网络阻断了 ICMP 报文,PMTUD可能失效,导致数据包总被丢弃。
~码文不易,留个赞再走吧~
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐

所有评论(0)