计网八股文
应用层
HTTP基础
HTTP(超文本传输协议 HyperText Transfer Protocol)是应用层协议,主要用于客户端与服务端之间的数据传输
HTTP默认使用80端口
特点:
- 基于请求 - 响应模型:客户端发起请求,服务端返回响应
- 无状态:HTTP协议本身不会记住两次请求是否来自同一个用户
- 明文传输:HTTP本身不加密,HTTPS才会加密
- 基于TCP:HTTP/1、HTTP/2基于TCP(HTTP/3基于QUIC)
HTTP请求报文结构:
1 | 请求行 |
例如:
1 | GET /user?id=1 HTTP/1.1 |
HTTP响应报文结构:
1 | 状态行 |
例如:
1 | HTTP/1.1 200 OK |
HTTP常见方法
| 方法 | 含义 |
|---|---|
| GET | 获取资源 |
| POST | 提交数据,通常用于创建资源 |
| PUT | 更新资源,通常是整体更新 |
| PATCH | 局部更新资源 |
| DELETE | 删除资源 |
| HEAD | 只获取响应头,不获取响应体,检测资源存在、获取文件大小 |
| OPTIONS | 查询服务端支持的方法,常用于CORS跨域预检 |
GET和POST区别
- GET通常用于查询,POST通常用于提交数据
- GET参数一般放在URL中,POST参数一般放在请求体中
- GET更容易被浏览器默认缓存,POST默认不会缓存
- GET语义上应该是幂等的,POST通常不是幂等的
- GET也可以有请求体,POST也可以把参数放URL里,这不是协议强限制,更多是语义和使用习惯
HTTP常见状态码
- 200:OK,请求成功
- 301:Moved Permanently,永久重定向
- 302:Found,临时重定向
- 304:Not Modified,资源未修改,客户端直接使用浏览器本地缓存
- 400:Bad Request,客户端请求格式错误
- 401:Unauthorized,身份未认证
- 403:Forbidden,身份已认证,但该身份没有访问权限
- 404:Not Found,资源不存在
- 500:Internal Server Error,服务器内部错误
- 503:Service Unavailable,服务暂时不可用(如服务器维护、过载熔断)
HTTP缓存
HTTP缓存(也称为浏览器缓存)分为强缓存和协商缓存
强缓存
强缓存命中时,浏览器直接使用本地缓存,不会向服务器发送请求
常见响应头:
1 | Cache-Control: max-age=3600 // 缓存有效时长 |
现代系统一般使用Cache-Control,如果二者同时存在,Cache-Control优先级高于Expires
协商缓存(Conditional Get)
强缓存失效后,浏览器会向服务端发请求,判断资源是否发生变化,如果未变化,继续使用浏览器本地缓存
基于修改时间
当浏览器第一次向服务端请求资源时,服务端返回资源时会在响应头中带上:
1
Last-Modified: Mon, 01 Jun 2026 10:00:00 GMT
Last-Modified表示资源最后一次修改的时间,浏览器会把资源和Last-Modified时间一起缓存起来客户端再次请求时,如果强缓存过期了,浏览器并不会直接丢弃缓存,而是向服务器发送请求询问缓存是否过期,在请求头中会携带:
1
If-Modified-Since: Mon, 01 Jun 2026 10:00:00 GMT
If-Modified-Since的值为浏览器缓存的Last-Modified的值,表示询问服务端在这个时间点之后,资源是否修改过。如果修改过,服务端会返回新的资源和新的Last-Modified,浏览器更新缓存和Last-Modified;如果没修改过,服务端会返回304状态码,表示资源未修改,浏览器直接使用本地缓存基于资源版本
当浏览器第一次向服务端请求资源时,服务端返回资源时会在响应头中带上:
1
ETag:"abc123"
ETag表示资源的唯一标识,可以理解为资源的版本号。服务端会根据返回的资源内容生成唯一的ETag,浏览器会把资源和Etag一起缓存起来客户端再次请求时,如果强缓存过期了,浏览器并不会直接丢弃缓存,而是向服务器发送请求询问缓存是否过期,在请求头中会携带
1
If-None-Match: "abc123"
If-None-Match的值为浏览器缓存的Etag的值,表示询问服务端资源是否发生了变化。如果ETag变化了,服务端会返回新的资源和新的Etag,浏览器更新缓存和Etag;如果没变,服务端会返回304状态码,表示资源未修改,浏览器直接使用本地缓存
ETag通常比Last-Modified更精确,因为Last-Modified只能精确到秒
HTTP/1.0、HTTP/1.1、HTTP/2、HTTP/3
HTTP/1.0
HTTP/1.0默认使用短连接
每发起一次HTTP请求,都要重新建立一次TCP连接,请求结束后再关闭连接
导致频繁进行TCP三次握手和四次挥手,开销比较大
HTTP/1.1
HTTP/1.1相比HTTP/1.0还有几个重要改进:
HTTP Keep-Alive
HTTP/1.1默认使用长连接
一个TCP连接建立后,可以复用这个连接发送多个HTTP请求,减少重复建立和关闭TCP连接的开销
支持
Host头Host表示客户端想访问哪个域名1
Host: www.example.com
为什么需要
Host?因为一台服务器 / 一个IP上可能部署多个网站,例如:
1
21.2.3.4 -> www.a.com
1.2.3.4 -> www.b.com如果请求里没有
Host,服务器只知道请求打到了1.2.3.4,但不知道用户想访问www.a.com还是www.b.com支持范围请求
客户端可以只请求资源的一部分,常用于断点续传、视频拖动
1
Range: bytes=0-999
表示只请求前1000个字节
HTTP/1.1的问题是存在应用层队头阻塞
HTTP/1.1虽然可以复用TCP连接,但同一个连接上的请求和响应整体上还是按顺序处理。如果前面的请求响应很慢,后面的请求就可能被阻塞
HTTP/2
HTTP/2主要解决HTTP/1.1在性能上的问题
二进制分帧
HTTP/1.1的报文是文本格式,HTTP/2把请求和响应拆成多个二进制帧来传输
多路复用
HTTP/2允许多个请求和响应在同一个TCP连接中并发传输,不同请求的数据帧可以交错发送,改善了应用层队头阻塞问题
头部压缩
HTTP请求头经常有大量重复出现的字段,例如
Cookie、User-Agent等。HTTP/2会对头部做压缩,减少传输开销服务器推送
HTTP/2支持服务端主动向客户端推送资源,例如客户端请求
index.html,服务端可以判断浏览器接下来大概率还需要style.css和main.js,于是提前把这些资源推送给客户端
HTTP/2解决了应用层的队头阻塞问题,但仍然存在TCP层的队头阻塞。TCP是有序的字节流协议,如果中间某个报文段丢失,即使后面的数据已经到达也不能直接交给应用层,必须等丢失的数据重传之后再按顺序交付
HTTP/3
HTTP/3不再基于TCP,而是基于QUIC协议
QUIC
QUIC是基于UDP实现的可靠传输协议
QUIC主要特点:
可靠传输
UDP本身不可靠,但QUIC在UDP之上实现了确认应答、重传、拥塞控制等机制,因此可以提供可靠传输
内置TLS
QUIC把TLS集成到了协议内部,默认加密,减少握手次数
传统HTTPS通常需要:
1
2
3
4
5TCP握手
↓
TLS握手
↓
HTTP请求QUIC可以把传输层握手和TLS握手合并,减少连接建立时延
解决TCP层队头阻塞
QUIC原生支持多路复用,每个stream可以独立传输、独立重传
如果某个stream的数据丢了,只会阻塞这个stream,不会阻塞其他stream
1
2
3stream A 丢包 -> 只影响 stream A
stream B 正常 -> 可以继续交付
stream C 正常 -> 可以继续交付支持连接迁移
TCP连接由四元组确定:
1
源IP + 源端口 + 目的IP + 目的端口
如果手机从WiFi切换到4G,IP变化,TCP连接通常会断开
QUIC使用连接ID标识连接,而不是只依赖四元组。即使客户端IP变化,只要连接ID不变,就可以继续复用原连接
HTTP Keep-Alive 和 TCP Keepalive
二者没什么联系,只是名字比较像:
HTTP Keep-Alive:
是HTTP/1.1引入的长连接机制,多个请求可以共用同一个TCP连接,减少TCP连接建立和关闭时的开销
TCP Keepalive:
是TCP层用于检测连接是否还存活的机制,例如客户端和服务端建立了TCP连接,但客户端突然断网 / 宕机,没有正常发送
FIN关闭连接。此时服务端可能一直以为这个TCP连接还存在,占用连接资源TCP Keepalive的做法是:当一个TCP长时间连接但没有任何数据传输时,操作系统会发送探测报文给对端,如果对方正常ACK,则连接正常存活;如果多次探测均无响应,则认为对端已经宕机了,关闭连接
HTTPS与HTTP
HTTPS (Hypertext Transfer Protocol Secure):HTTPS 通过在HTTP协议和TCP协议之间加入TLS协议层来实现安全传输。它确保数据在客户端和服务器之间的传输过程中得到加密、身份验证和完整性保护,使得数据在传输过程中无法被窃听或篡改
HTTPS 默认使用 443 端口
1 | HTTPS = HTTP + TLS |
| 对比 | HTTP | HTTPS |
|---|---|---|
| 安全性 | 明文传输,不安全 | TLS加密,更安全 |
| 端口 | 80 | 443 |
| 证书 | 不需要 | 需要CA证书 |
| 强制性 | HTTP/1.1、HTTP/2可明文传输 | HTTP/3强制使用HTTPS HTTP/2默认需要HTTPS |
HTTPS连接过程
HTTPS连接过程可以理解为:先建立TCP连接,再进行TLS握手,最后传输数据
TLS的握手过程如下:
- Client Hello(客户端问候):客户端发送Client Hello消息,消息中包含客户端支持的TLS版本、加密算法列表、客户端随机数等信息
- Server Hello(服务端问候):服务端从客户端提供的加密算法列表中选择一个,并返回服务端的TLS版本、选择的加密算法、服务端随机数等信息
- Certificate(服务端发送证书):服务端将自己的证书发送给客户端,证书中包含了服务端的公钥、域名、有效期等信息
- Certificate Verification(证书验证):客户端验证服务端的证书是否有效(包括:证书是否由信任的CA机构颁发、证书是否过期、证书上的域名是否和服务器域名一致)
- Key Exchange(密钥交换):客户端生成一个预主密钥(Pre-master secret),使用服务器证书中的公钥对其进行加密,然后发送给服务器。服务端收到加密的预主密钥后,使用自己的私钥进行解密,获取明文的预主密钥(这里的公钥和私钥属于非对称加密)
- Session Key Generation(生成会话密钥):客户端和服务端都会利用交换过的客户端随机数、服务端随机数、预主密钥,通过相同的加密算法生成相同的会话密钥(对称密钥),用于后续数据的加密传输
- Change Cipher Spec(切换加密模式):客户端和服务端分别发送一条消息,通知对方后续使用加密通信
- Finished(完成):客户端和服务端分别发送加密过的Finished消息,验证加密和解密是否正确,若正确则TLS握手成功
WebSocket
WebSocket是一种全双工通信协议,连接建立后,客户端和服务端都可以主动发送消息
WebSocket通过HTTP升级建立连接:
1 | 客户端发送HTTP请求 |
适用场景:
- 即时通讯
- 在线游戏
SSE
SSE(Server-Sent Events)本质上是HTTP长连接
客户端先发起一个HTTP请求,服务端不立刻结束响应,而是保持这个HTTP连接,在同一个响应流中持续分段返回事件数据
这个HTTP长连接后续只能由服务端返回响应数据流,客户端不能再通过这个连接向服务端发送数据
适用场景:
- 消息通知
- 日志推送
- 服务端任务进度推送
DNS
DNS用于把域名解析成IP地址
1 | www.example.com -> 93.184.216.34 |
DNS解析大致流程:
1 | 浏览器缓存 |
DNS通常基于UDP 53端口,因为请求响应小,UDP开销低。如果响应太大、区域传送等场景,也会使用TCP
有了HTTP,为什么还需要RPC?
RPC并不是替代HTTP,很多RPC框架本身就运行在HTTP/2之上。两者最大的区别是HTTP面向资源,而RPC面向方法调用。微服务内部更倾向于RPC,因为RPC通常使用Protobuf等二进制协议,序列化效率更高、网络开销更小,并且能够通过IDL自动生成客户端代码,实现强类型调用。此外RPC框架一般内置服务发现、负载均衡、熔断、重试等服务治理能力,更适合大规模服务间通信。而对外接口通常采用HTTP,因为浏览器、APP和第三方系统天然支持HTTP协议
传输层
互联网的传输层常用协议主要有两种:TCP和UDP
- TCP(Transmission Control Protocol,传输控制协议)是面向连接,可靠,基于字节流的传输层协议
- UDP(User Datagram Protocol, 用户数据报协议)是无连接,不可靠,基于数据报的传输层协议
TCP vs UDP
| 对比项 | TCP | UDP |
|---|---|---|
| 是否连接 | 面向连接 | 无连接 |
| 是否可靠 | 可靠 | 不可靠 |
| 是否有序 | 有序 | 无序 |
| 是否重传 | 会 | 不会 |
| 速度 | 较慢 | 较快 |
| 传输数据形式 | 字节流 | 数据报 |
| 是否流量控制 | 有 | 无 |
| 是否拥塞控制 | 有 | 无 |
| 头部长度 | 20 - 60 字节 | 8 字节 |
UDP简介
UDP是在应用层数据的基础上,增加了一个很简单的UDP头部,UDP传输的数据单位叫数据报(datagram)
UDP头部固定8字节,包含四个字段:
| 字段 | 含义 |
|---|---|
| 源端口 | 发送方进程端口 |
| 目的端口 | 接收方进程端口 |
| 长度 | UDP数据报的总长度 |
| 校验和 | 用于检查UDP数据报是否在传输中出错 |
UDP只是简单的在网络层IP协议之上补充了两个机制:
多路复用 / 多路分解
IP只能把数据包送到目标主机,但不知道应该交给哪个进程。UDP通过端口号把数据交给对应的应用进程
1
2目的IP:找到主机
目的端口:找到进程多路复用:多个应用进程的数据可以通过UDP交给网络层发送
多路分解:接收方根据目的端口,把收到的数据交给对应的应用进程
对UDP来说,接收方通常根据二元组找到对应的socket:
1
目的IP + 目的端口
也就是说,只要发到同一个本机IP和同一个UDP端口,就会被交给同一个UDP socket处理
数据完整性校验
UDP通过校验和检查数据在传输过程中是否发生错误。如果校验失败,说明数据可能被破坏,接收方会直接丢弃该数据报
TCP简介
TCP是在应用层数据的基础上,增加了一个TCP头部,TCP传输的数据单位叫报文段(segment);TCP提供的机制比UDP复杂得多
TCP头部长度通常为20 - 60字节,常见的头部信息有:
| 字段 | 含义 |
|---|---|
| 源端口 | 发送方进程端口 |
| 目的端口 | 接收方进程端口 |
| 序列号 | 标识发送数据的字节位置 |
| 确认号 | 表示期望收到的下一个字节序号 |
| 头部长度 | TCP头部的长度(因为大小不固定 所以需要指明) |
| 标志位 | 如SYN、ACK、FIN、RST等 |
| 窗口大小 | 接收方还能接收多少数据,用于流量控制 |
| 校验和 | 检查TCP报文段是否出错 |
| 选项(可选) | 如MSS、窗口扩大、时间戳等 |
TCP与UDP一样,也提供多路复用 / 多路分解和数据完整性校验两个机制
TCP中一个连接由四元组唯一确定:
1 | 源IP + 源端口 + 目的IP + 目的端口 |
只要四元组相同,就认为是同一条TCP连接;只要其中任意一个不同,就是不同连接
例如同一个客户端可以和同一个服务端建立多个TCP连接,只要客户端源端口不同即可:
1 | 192.168.1.10:50001 -> 10.0.0.1:80 |
需要注意,服务端用于监听端口的TCP socket只是监听socket,它负责接收新的连接请求。每当有客户端完成三次握手后,内核会为这个连接创建一个新的连接socket用于通信,这个连接socket才对应一条具体的四元组TCP连接
1 | 监听socket:0.0.0.0:80 |
UDP没有建立连接的过程,也不会为每个客户端创建一个新的连接socket。多个客户端发到同一个UDP端口的数据,通常都会交给同一个UDP socket
除此之外,TCP还实现了更多机制来保证可靠、有序地传输数据:
- 面向连接:三次握手建立连接,四次挥手关闭连接
- 序列号和确认应答:确认哪些数据已经被对方收到
- 超时重传:数据超时丢失后重新发送
- 快速重传:推测数据丢失后快速重新发送
- 滑动窗口:允许连续发送多个报文段,提高吞吐量
- 流量控制:根据接收方缓冲区大小控制发送速度
- 拥塞控制:根据网络拥塞情况调整发送速度
- 有序交付:即使报文乱序到达,也会按顺序交给应用层
TCP连接
三次握手
TCP建立连接的过程是三次握手
客户端主动向服务端建立连接的过程如下:
第一次握手
客户端向服务端发送:
1
2SYN=1 // 标志位
seq=x // 序列号含义:客户端想要建立连接,向服务端发送请求
之后客户端进入
SYN_SENT状态第二次握手
服务端回复:
1
2
3
4SYN=1 // 标志位
ACK=1
seq=y // 序列号
ack=x+1 // 确认号含义:服务端收到了客户端的请求,服务端也想建立连接,回复客户端
之后服务端进入
SYN_RCVD状态第三次握手
客户端发送:
1
2
3ACK=1
seq=x+1
ack=y+1含义:客户端收到了服务端的回复
之后客户端进入
ESTABLISHED状态,服务端收到该报文段后,也进入ESTABLISHED状态至此连接建立
为什么必须三次握手?
要确认双方都具有正常发送和接收的能力
- 第一次握手:客户端能发
- 第二次握手:服务端能收,服务端能发
- 第三次握手:客户端能收
如果只有两次握手?
如果服务器响应完客户端之后就结束了,此时可能客户端根本没收到服务端的响应,客户端会认为连接失败,但服务端却认为连接已经建立了
如果有四次握手?
三次握手已经能确保双方都具有收发的能力了,第四次多余
四次挥手
TCP断开连接的过程是四次挥手
客户端主动向服务端关闭连接的过程如下:
第一次挥手
客户端向服务端发送:
1
2FIN=1
seq=u含义:客户端没有需要发送的数据了,想要断开连接
之后客户端进入
FIN_WAIT_1状态第二次挥手
服务端回复:
1
2ACK=1
ack=u+1含义:服务端收到了客户端的请求,但服务端可能还有没发送完的数据
之后服务端进入
CLOSE_WAIT状态,客户端收到该回复后进入FIN_WAIT_2状态第三次挥手
服务端处理完剩余数据,没有要发送的数据之后,会向客户端发送:
1
2
3FIN=1
seq=v
ack=u+1含义:服务端处理完了剩余数据,也想要断开连接
之后服务端进入
LAST_ACK状态第四次挥手
客户端回复:
1
2
3ACK=1
seq=u+1
ack=v+1含义:客户端收到了服务端的请求
之后客户端进入
TIME_WAIT状态,服务端收到该回复后,进入CLOSED状态客户端进入
TIME_WAIT状态后会等待一段时间(建议4min),等待结束后客户端再进入CLOSED状态至此连接断开
第四次挥手的必要性
第四次挥手是客户端向服务端FIN报文的ACK,如果缺少这一步,服务端发送FIN后无法确认客户端是否收到了该FIN,如果该FIN丢失,客户端将一直处于FIN_WAIT_2状态;因此需要第四次挥手ACK,服务端知道自己需要收到该ACK才会断开连接,哪怕该ACK丢失,服务端也可以通过收不到ACK的超时重传机制来重传FIN,第四次挥手确保连接能被完整关闭
如果只有三次挥手?
三次挥手一般指合并第二次和第三次挥手。客户端发送FIN后,服务端需要先回复ACK(第二次挥手),然后发送没处理完的数据,等待没有数据要发送,再发送FIN(第三次挥手)。如果合并第二次和第三次挥手,直接发FIN+ACK,可能会导致服务端没发送完的数据丢失
如果有五次挥手?
TCP是全双工通信,四次挥手,客户端和服务端各发送一次FIN和ACK,能明确关闭双向连接。五次挥手只会做重复确认,多余,不会提高可靠性
为什么客户端要有TIME_WAIT状态?
- 防止最后的ACK丢失,如果ACK丢失,服务端会重发FIN,客户端可以重新回复ACK
- 等待网络中的旧报文彻底消失,防止旧连接数据污染新连接;如果立刻关闭连接,可能还会有旧连接的数据包在发送的途中,此时如果又建立了相同四元组的新连接,可能会导致新连接收到旧连接的数据
TCP可靠传输:序列号、确认号、重传、有序交付
网络包在传输过程中可能出现丢包、重复、乱序的问题,TCP的可靠传输能保证应用层读到的是连续、不重复、有序的字节流
TCP主要依靠序列号 + 确认号 + 充传 + 接收缓冲区来实现可靠传输
序列号
TCP是基于字节流传输的,序列号用于标识每个报文段的第一个数据字节在字节流中的位置
例如发送方发送100字节数据:
1 | seq=1000 |
表示这段数据在字节流中的覆盖范围是1000 - 1099
确认号
接收方收到报文段后,会通过ACK告诉发送方:我已经连续收到了哪些数据,下一次希望你从哪里开始发
1 | ack=1100 |
表示1100之前的数据我都连续收到了,下一次希望你从1100开始发
TCP使用的是累计确认,确认号表示**“这个确认号之前的数据都已经连续收到”**
丢包与重传
如果数据丢了,发送方会重新发送,通常有两种重传机制:
超时重传
发送方发送一个报文段后,会启动一个计时器。如果在规定时间内没有收到这个报文段对应的ACK,就认为该报文段可能丢失,于是重新发送
快速重传
TCP使用累计确认,如果中间某个报文段丢了,接收方后续又收到了新的报文段,由于前面缺失了一段数据,接收方无法把确认号向后推进,只能继续回复**“最后一个连续收到的位置”**
例如发送方发送:
1
1 2 3 4 5
其中3丢失了,接收方收到:
1
2
3
4收到1 -> ack=2
收到2 -> ack=3
收到4 -> ack=3
收到5 -> ack=3如果发送方连续收到了3个相同的重复ACK,就认为对应的数据可能丢失,不必等待超时,直接重传丢失的数据
乱序和有序交付
网络中报文段可能乱序到达。TCP会根据序列号判断数据位置,把乱序到达的数据先放到接收缓冲区,等缺失的数据到达后,再按顺序交给应用层
例如发送方发送:
1 | 1 2 3 4 |
接收方先收到:
1 | 1 3 4 |
此时3 4不会立刻交给应用层,而是先放在接收缓冲区里,等待2到达。等2到达后,再按顺序交付:
1 | 1 2 3 4 |
TCP如何保证接收方避免重复接收相同数据?
接收方会记录自己已经连续接收到的位置,以及接收缓冲区中已经收到但还没交付给应用层的数据。如果收到一个报文段,发现它的序列号范围已经接收过,就会认为这是重复数据,直接丢弃,正常回复已经连续接收到的数据期待的下一个字节位置的ACK
TCP滑动窗口
如果TCP每发送一段数据都要等ACK回来,再发送下一段数据,效率会很低
滑动窗口的作用是:允许发送方在没有收到ACK之前,连续发送一批数据
接收方和发送方都会维护一个滑动窗口:
发送窗口 swnd
发送窗口swnd是发送方实际能发送的数据范围。窗口内的数据可以发送,窗口外的数据暂时不能发送
发送方的数据可以分成四段:
1 | 已发送且已确认 | 已发送但未确认 | 可以发送但还没发送 | 暂时不能发送 |
其中:
1 | 已发送但未确认 + 可以发送但还没发送 = 发送窗口 swnd |
当发送方收到ACK后,说明前面一部分数据已经被接收方确认,窗口就可以向后移动,这就是“滑动”
接收窗口 rwnd
接收窗口rwnd是接收方告诉发送方:我的接收缓冲区还剩多少空间
接收方应用程序从接收缓冲区读取数据后,缓冲区空闲空间变大,rwnd就变大;如果应用程序读取得慢,缓冲区快满了,rwnd就变小
接收方会把rwnd放在TCP首部的窗口大小字段中,通知发送方
TCP流量控制
流量控制就是通过rwnd控制发送方的发送速度,避免发送方发太快,把接收方的接收缓冲区打满
发送方的发送窗口不能超过接收方通告的接收窗口:
1 | swnd <= rwnd |
如果接收方通告rwnd = 0,发送方会暂停发送数据。但为了避免双方一直等待,发送方会定期发送窗口探测报文,询问接收方窗口是否恢复
TCP拥塞控制
流量控制关注的是接收方能不能接得住,拥塞控制关注的是网络能不能承受
如果网络中发送的数据太多,路由器、交换机等设备处理不过来,就可能出现丢包和延迟升高。TCP会通过拥塞控制动态调整发送速度
拥塞控制主要维护一个拥塞窗口(cwnd)
发送方实际发送窗口通常受两个窗口共同限制:
1 | 发送窗口 = min(接收窗口rwnd, 拥塞窗口cwnd) |
常见拥塞控制过程:
慢启动
TCP刚开始不知道网络能承受多少数据,所以让
cwnd从一个较小的值开始增长,收到ACK后就会快速增长,指数级增长1
1 -> 2 -> 4 -> 8
拥塞避免
当
cwnd达到**慢启动门限(ssthresh)**后,增长速度从指数增长变成线性增长1
8 -> 9 -> 10 -> 11
拥塞发生
TCP判断网络拥塞主要看丢包,而丢包通常通过两种方式发现:
超时重传
如果发送方等了很久都没收到ACK,触发超时重传;TCP会认为网络拥塞较为严重
这时TCP会降低慢启动门限,让拥塞窗口从一个小值开始重新进入慢启动
1
2
3ssthresh = cwnd / 2
cwnd = 1
重新进入慢启动快速重传
如果发送方连续收到3个重复ACK,会触发快速重传;TCP会认为虽然有包丢了,但是接收方还能收到后续包,网络不一定完全拥塞,只是出现了局部丢包
这时TCP不会让拥塞窗口重新慢启动,而是进行快速恢复
快速恢复
快速恢复是配合快速重传使用的,当发送方收到3个重复ACK后
这时TCP同样会降低慢启动门限,但会让拥塞窗口降低到慢启动门限附近(即减半),进入拥塞避免阶段,线性增长
1
2
3
4
5ssthresh = cwnd / 2
↓
cwnd 降低到 ssthresh 附近
↓
进入拥塞避免,继续线性增长简单对比:
| 丢包发现方式 | TCP认为 | 处理方式 |
| ———— | ——- | ——– |
| 超时重传 | 拥塞比较严重 | cwnd降到很小,重新慢启动 |
| 快速重传 | 可能只是局部丢包 | cwnd减半,进入拥塞避免 |
TCP粘包(zhan)
TCP是面向字节流的协议,它只保证字节数据可靠、有序地到达,但不保证应用层消息的边界
应用层交给TCP的数据会先被放到发送缓冲区,TCP会根据这些数据构造报文段,这时可能会对应用层的消息进行拆分或者合并。TCP不知道应用层消息的边界,只会把应用层消息当作连续的字节流传输,因此接收方无法直接区分应用层消息的边界,这就是TCP的粘包 / 拆包问题
解决思路是应用层需要自己定义消息边界
常见方案:
固定长度
每条消息固定长度,不足补齐;解析简单,但会浪费空间
分隔符
使用特殊字符作为消息结束标志,例如
\n;如果消息中也含有分隔符需要手动转义1
hello\nworld\n
消息头 + 消息体长度
在消息头中记录消息体长度,接收方先读固定长度的消息头,再根据长度读取消息体
1
2length=5 | hello
length=5 | world这是最常见的方案,例如HTTP协议读取消息头时采用
\r\n\r\n分隔符,在消息头中携带Content-Length标记消息体长度
网络层
网络层主要解决的问题是:如何把数据包从一台主机送到另一台主机
传输层关注的是进程到进程,例如端口;网络层关注的是主机到主机,例如IP地址、路由、网关
1 | 传输层:进程 -> 进程,靠端口 |
IP地址
IP地址用于标识网络中的一台主机
常见的IPv4地址如下:
1 | 192.168.1.10 |
IPv4是32位地址,通常写成4段十进制数字。IP地址本身可以分为两部分:
1 | 网络号 + 主机号 |
- 网络号:表示这个IP属于哪个网络
- 主机号:表示这个网络中的哪台主机
例如:
1 | 192.168.1.10/24 |
/24表示前24位是网络号,后8位是主机号,因此它所在的网段是:
1 | 192.168.1.0/24 |
子网掩码
子网掩码用于判断两个IP是否在同一个网段
例如:
1 | IP:192.168.1.10 |
255.255.255.0也可以写成/24,表示前24位是网络号
判断两个IP是否在同一个子网,本质是:分别和子网掩码做与运算,看网络号是否相同
1 | 192.168.1.10/24 -> 网络号 192.168.1.0 |
网络号相同,说明在同一个子网,可以直接在局域网内通信
1 | 192.168.1.10/24 -> 网络号 192.168.1.0 |
网络号不同,说明不在同一个子网,需要通过网关转发
网关
网关是主机访问其他网段时的出口
如果目标IP和自己在同一个子网,主机可以直接通过ARP找到目标主机的MAC地址,然后直接发送
如果目标IP和自己不在同一个子网,主机不能直接把包发给目标主机,而是先把包交给默认网关
1 | 同一子网:ARP找目标主机MAC,直接发送 |
例如主机A:
1 | IP:192.168.1.10/24 |
由于8.8.8.8不在192.168.1.0/24网段内,所以主机A会把数据包交给网关192.168.1.1,由网关继续转发
需要注意:
- IP包的目的IP仍然是最终目标IP
- 以太网帧的目的MAC是下一跳设备的MAC
也就是说,跨网段通信时,IP目的地址不变,但每一跳的MAC地址会变
路由
路由决定一个IP数据包下一步应该发到哪里
主机或路由器发送数据包时,会查路由表:
1 | 目标IP |
路由表中常见信息:
- 目标网段
- 子网掩码
- 下一跳地址
- 出口网卡
如果没有更精确的路由,就走默认路由,也就是默认网关
1 | default via 192.168.1.1 |
表示不知道怎么走的包,都交给192.168.1.1
ARP
ARP用于在同一个局域网内,根据IP地址找到MAC地址
1 | IP地址 -> MAC地址 |
为什么需要ARP?
因为IP负责网络层寻址,但在局域网中真正发送以太网帧时,需要知道下一跳的MAC地址
例如主机A想发数据给同一局域网内的主机B:
1 | A:192.168.1.10 |
A只知道B的IP,不知道B的MAC,于是发送ARP广播:
1 | 谁是192.168.1.20?请告诉192.168.1.10 |
B收到后回复自己的MAC地址。A拿到后会缓存到ARP缓存表中,后续就不用每次都广播
1 | 192.168.1.20 -> aa:bb:cc:dd:ee:ff |
需要注意:ARP只能在局域网内使用。如果目标不在同一个网段,主机会ARP查询网关的MAC,而不是目标主机的MAC
交换机
交换机工作在数据链路层,主要根据MAC地址转发以太网帧
交换机会维护一张MAC地址表:
1 | MAC地址 -> 交换机端口 |
交换机收到一个以太网帧后:
- 如果知道目标MAC在哪个端口,就只转发到对应端口
- 如果不知道目标MAC在哪个端口,就广播到其他端口
交换机会通过学习源MAC地址来维护MAC地址表
例如交换机从1号端口收到一个帧,源MAC是aa:aa:aa,它就知道:
1 | aa:aa:aa -> 1号端口 |
简单理解:
1 | 路由器 / 网关:看IP,负责跨网段转发 |
NAT
NAT用于网络地址转换,常见于内网访问公网
内网主机通常使用私有IP,例如:
1 | 192.168.x.x |
这些私有IP不能直接在公网路由,因此访问公网时需要经过NAT设备,把内网IP和端口转换为公网IP和端口
1 | 192.168.1.10:50000 -> 公网IP:30001 |
NAT设备会维护一张映射表:
1 | 内网IP:内网端口 <-> 公网IP:公网端口 |
响应包从公网回来时,NAT设备根据映射表再把数据转回对应的内网主机
NAT的作用:
- 缓解IPv4地址不足
- 多个内网设备共享一个公网IP
- 隐藏内网结构
NAT的问题:
- 破坏了端到端通信模型
- 外部主机主动访问内网主机比较困难
- P2P、音视频、游戏等场景可能需要NAT穿透
IPv6
IPv6主要是为了解决IPv4地址不够的问题
IPv4是32位地址,IPv6是128位地址,地址空间非常大
IPv6地址示例:
1 | 2001:0db8:85a3:0000:0000:8a2e:0370:7334 |
IPv6特点:
- 地址空间更大
- 头部设计更简化
- 更好支持自动配置
- 理论上减少对NAT的依赖,每台设备都可以有公网地址
但IPv6和IPv4不直接兼容,所以实际迁移需要双栈、隧道、NAT64等过渡方案
DHCP
DHCP用于给主机自动分配网络配置
常见分配内容:
- IP地址
- 子网掩码
- 默认网关
- DNS服务器地址
DHCP过程通常记为DORA:
1 | Discover:客户端广播寻找DHCP服务器 |
为什么刚开机时需要广播?
因为客户端一开始还没有IP地址,也不知道DHCP服务器是谁,所以只能通过广播寻找DHCP服务器
ICMP 和 ping
ICMP(Internet Control Message Protocol)是互联网控制报文协议,属于网络层协议
IP协议本身只负责尽力把数据包从源主机送到目标主机,但它不负责告诉发送方“为什么失败”。如果网络中出现不可达、超时、TTL耗尽等情况,就需要ICMP来返回错误信息
简单理解:
1 | IP:负责送包 |
ICMP常见报文类型:
| 类型 | 含义 |
|---|---|
| Echo Request | 回显请求,ping使用 |
| Echo Reply | 回显响应,ping使用 |
| Destination Unreachable | 目标不可达 |
| Time Exceeded | TTL耗尽,常用于traceroute |
| Redirect | 路由重定向 |
ping的工作原理
ping底层使用ICMP Echo Request和Echo Reply
1 | 本机 -> 目标主机:Echo Request |
本机发送一个ICMP Echo Request报文给目标主机,目标主机收到后返回ICMP Echo Reply。本机根据是否收到响应、响应耗时、丢包情况来判断网络状态
ping可以测试:
- 目标主机是否可达
- 网络延迟
- 是否有丢包
ping输出中的常见信息:
1 | 64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=20.3 ms |
icmp_seq:ICMP请求序号,用于区分第几次pingttl:剩余TTL值,表示数据包还能经过多少跳time:往返耗时RTT
TTL是什么?
TTL表示IP数据包还能经过多少个路由器。每经过一个路由器,TTL减1。如果TTL变成0,路由器会丢弃该包,并返回ICMP Time Exceeded报文
TTL的作用是防止数据包因为路由环路在网络中无限转发
但ping不通不一定代表服务不可用,可能是:
- 目标机器禁用了ICMP
- 防火墙拦截了ICMP
- 中间网络设备禁止ICMP
反过来,ping通也不代表业务服务可用,因为业务端口可能没开,或者应用已经异常
输⼊⽹址到⻚⾯显示发⽣了什么?
以访问:
1 | https://www.example.com/index.html |
为例,大致流程:
1 | 浏览器解析URL |
浏览器解析URL
浏览器会解析协议、域名、端口、路径
1
2
3
4协议:https
域名:www.example.com
端口:443
路径:/index.html检查浏览器缓存
如果命中强缓存,浏览器可以直接使用本地缓存,不需要发请求
如果强缓存失效,可能走协商缓存,向服务器确认资源是否变化
DNS解析
浏览器需要把域名解析成IP地址
1
浏览器缓存 -> 操作系统缓存 -> 本地DNS -> 根DNS -> 顶级域DNS -> 权威DNS
TCP三次握手
拿到服务端IP后,客户端和服务端通过三次握手建立TCP连接,确认双方收发能力正常,并同步初始序列号
TLS握手
如果是HTTPS,还需要TLS握手
发送HTTP请求
浏览器发送HTTP请求,例如:
1
2GET /index.html HTTP/1.1
Host: www.example.com服务端处理请求
服务端可能经过Nginx、网关、业务服务、数据库、缓存等组件,最终生成HTTP响应
浏览器接收响应并渲染页面
浏览器解析HTML,渲染界面
如果HTML中引用CSS、JS、图片等资源,浏览器还会继续发起新的请求获取这些资源
Linux系统收发网络包的过程?
接收网络包
Linux接收网络包的大致流程:
1 | 网卡收到数据包 |
关键点:
网卡和DMA
网卡收到数据后,不需要CPU一个字节一个字节搬运,而是通过DMA把数据写入内存中的Ring Buffer
硬中断
网卡把数据写入内存后,通过硬中断通知CPU:有网络包到了
硬中断要求尽快返回,不能做太多耗时工作
软中断
Linux会把耗时的网络协议栈处理放到软中断中完成,例如解析以太网头、IP头、TCP/UDP头等
协议栈处理
内核根据协议字段逐层分发:
1
以太网类型 -> IP协议 -> TCP/UDP端口 -> socket
socket接收缓冲区
找到对应socket后,内核会把数据放入socket接收缓冲区,应用程序通过
read/recv读取
发送网络包
Linux发送网络包的大致流程:
1 | 应用程序调用write/send |
关键点:
- 发送数据通常涉及系统调用,会从用户态进入内核态
- TCP发送时会先进入socket发送缓冲区,由内核协议栈负责分段、重传、拥塞控制等
- IP层负责查路由,确定下一跳
- 链路层负责根据下一跳MAC封装以太网帧
- 网卡最终通过DMA读取内存中的数据并发送
简单理解:
1 | 接收:网卡 -> 内核协议栈 -> socket接收缓冲区 -> 应用 |
Linux常用网络命令,网络故障排查思路
常用网络命令
查看IP地址和网卡信息
1
2ip addr
ifconfig查看路由表
1
2ip route
route -n测试网络连通性
1
ping 8.8.8.8
查看DNS解析
1
2nslookup example.com
dig example.com查看端口监听和连接状态
1
2ss -antp
netstat -antp测试端口是否可达
1
2telnet host port
nc -vz host port发送HTTP请求排查问题
1
curl -v https://example.com
抓包分析
1
2tcpdump -i eth0 port 80
tcpdump -i eth0 host 1.2.3.4查看链路路径
1
2traceroute example.com
tracepath example.com
网络故障排查思路
排查网络问题时,建议从下往上、从本机到远端逐层排查
本机网络配置是否正常
1
2ip addr
ip route检查IP、子网掩码、默认网关是否正确
DNS是否正常
1
2nslookup domain
dig domain如果域名解析失败,但直接访问IP正常,通常是DNS问题
网络是否可达
1
2ping ip
traceroute ip用于判断目标是否可达、路径是否异常
端口是否可达
1
2nc -vz host port
telnet host portping通不代表业务端口可用,端口不通可能是服务没启动、防火墙拦截、安全组限制等
服务是否正常响应
1
curl -v http://host:port/path
可以查看HTTP状态码、响应头、TLS握手、重定向等信息
本机连接状态是否异常
1
ss -antp
重点关注:
- 是否有大量
TIME_WAIT - 是否有大量
CLOSE_WAIT - 是否有大量
SYN_SENT - 监听端口是否存在
- 是否有大量
抓包确认请求是否真正发出 / 响应是否回来
1
tcpdump -i eth0 host 目标IP and port 端口
抓包可以确认问题发生在哪一段:
- 请求是否发出
- 对端是否响应
- 是否发生重传
- 是否有RST
- TLS握手是否成功
常见现象:
| 现象 | 可能原因 |
|---|---|
| DNS解析失败 | DNS服务器异常、域名配置错误 |
| ping不通 | ICMP被禁、防火墙、路由问题、目标不可达 |
| ping通但端口不通 | 服务没启动、防火墙、安全组、端口监听错误 |
大量CLOSE_WAIT |
应用没有正确关闭连接 |
大量TIME_WAIT |
短连接太多,主动关闭连接的一方较多 |
大量SYN_SENT |
对端端口不通、网络不通、防火墙丢包 |
| HTTP 502 | 网关无法正常访问上游服务 |
| HTTP 504 | 网关访问上游服务超时 |
说些什么吧!