传输层位于OSI七层模型第四层,核心作用:端到端进程通信,通过端口号区分主机上不同应用进程,向上层应用提供通信服务。传输层两大核心协议:UDP、TCP。
一、UDP 协议(用户数据报协议)
1. UDP报文头部格式(8字节固定头部)
UDP头部一共8字节,没有可选字段:
| 字段 | 长度 | 说明 |
|---|---|---|
| 源端口 | 2字节 | 发送方进程端口号 |
| 目的端口 | 2字节 | 接收方进程端口号 |
| 数据报长度 | 2字节 | 整个UDP报文总长度(头部+数据),最小8字节,最大65535字节,UDP最大报文64K,数据部分最大 64K‑8字节 |
| 校验和 | 2字节 | 校验报文是否损坏,二进制反码求和算法,可置0不校验 |
校验和计算流程:
- 填充伪报文头部,把校验和字段置0;
- 对报文所有数据按16bit逐字取反相加求和;
- 结果填入校验和字段;
- 接收方同样做反码求和,结果为0代表数据未损坏。
2. UDP核心特性
- 无连接:通信前不需要建立连接,不需要维护对方连接状态,只需要知道IP+端口就直接发数据。
- 不可靠传输:不保证报文一定到达、不保证顺序、不保证不重复,没有确认、重传机制,丢包不会重发。
- 面向数据报:应用层给多少数据,UDP就打包一个报文发送;接收方必须一次性读取完整报文,不存在读半个包、粘包问题,但是存在数据报分片问题。
重点:UDP是整块打包,你sendto一次就是一个UDP数据包,recvfrom必须一次收完整包,不能分次读取。
3. UDP编程要点
- 可靠性需要应用层自己实现:序号、ack确认、丢包重传、超时处理都要代码自己写,内核UDP本身不管可靠。
- 报文长度限制:单包最大64K;如果业务数据大于64K,应用层手动分包,切割成小块发送。
- 支持广播、组播:无连接特性,可以一次sendto发给局域网所有主机(广播),也支持组播。
- 不会产生粘包,但是会丢包、乱序。
4. UDP适用场景
适合对实时性要求高,可以容忍少量丢包场景:直播、语音通话、视频会议、DNS查询。
二、TCP协议(传输控制协议)
1. TCP报文头部格式
TCP头部最小20字节,最大60字节,有40字节可选选项。
| 字段 | 长度 | 说明 |
|---|---|---|
| 源端口 | 2字节 | 发送进程端口 |
| 目的端口 | 2字节 | 接收进程端口 |
| 序列号 seq | 4字节 | 当前报文数据第一个字节的编号,实现有序传输 |
| 确认号 ack | 4字节 | 期望收到下一字节的序列号,ack = 对方seq + 数据长度 |
| 头部长度 | 4bit | 以4字节为单位,代表TCP头部总长度;值*4才是真实字节数 |
| 保留位 | 6bit | 保留,默认0 |
| 6位标志位 | 6bit | URG、ACK、PSH、RST、SYN、FIN |
| 窗口大小 | 2字节 | 滑动窗口大小,流量控制,我方还能接收多少字节数据 |
| 校验和 | 2字节 | 校验整个TCP报文(伪头部+头部+数据) |
| 紧急指针 | 2字节 | URG有效时,偏移量,标记紧急数据末尾位置 |
| 选项 | 0‑40字节 | MSS、窗口扩大、时间戳等协商信息 |
TCP 6个标志位详解
- URG:紧急指针有效,带外紧急数据,不用等待缓冲区读完。
- ACK:确认标志,除三次握手第一个SYN包,几乎所有报文ACK=1。
- PSH:推送,接收方收到立刻交给应用层,不要缓存等待。
- RST:重置连接,异常强制断开,拒绝非法报文。
- SYN:同步序列号,建立连接,握手报文。
- FIN:结束,代表本方不再发送数据(还可以接收对方数据,半关闭)。
2. TCP核心特性
- 面向连接:通信必须先建立连接,结束要释放连接;内核维护连接状态。
- 可靠传输:保证数据完整、有序、不重复到达;依靠:序列号+ACK确认、超时重传、校验和。
- 面向字节流:应用层数据看作一串无边界字节流;存在粘包问题,内核缓冲区合并数据,接收端不知道原始消息边界。
- 流量控制:滑动窗口:接收方通过窗口大小告诉发送方自己缓冲区剩余容量,防止发送方发太快淹没接收端。
- 拥塞控制:慢启动、拥塞避免、快重传、快恢复,防止网络过载。
粘包:TCP没有消息边界,多次write的数据在内核缓冲区合并,读出来分不清哪次是哪条消息,需要应用层处理:定长、分隔符、头部+长度。
3. TCP三次握手(建立连接)
客户端主动发起连接,服务端listen等待连接。
- 第一次握手:客户端发送
SYN=1,seq=x → 客户端状态:SYN_SENT。 - 第二次握手:服务端回复
SYN=1,ACK=1,seq=y,ack=x+1 → 服务端状态:SYN_RCVD。 - 第三次握手:客户端回复
ACK=1,ack=y+1 → 双方进入ESTABLISHED,正式可以收发数据。
为什么是三次握手,不是两次?
两次握手无法解决历史延迟的旧SYN报文问题。旧延迟SYN到达服务器,服务器回复SYN+ACK,如果两次握手就直接建立连接,但是客户端根本没有发起这次连接,会造成无效连接占用服务器资源。第三次握手客户端回复ACK,服务端收到ACK才真正建立连接,过滤过期报文。
4. TCP四次挥手(断开连接)
TCP是全双工,双方可以独立关闭自己的发送方向,可以半关闭。
- 第一次挥手:客户端发送
FIN=1,seq=m,客户端不再发送数据,进入FIN_WAIT_1。 - 第二次挥手:服务端回复
ACK=1,ack=m+1,状态变为CLOSE_WAIT。此时服务端还可以向客户端发送数据(半关闭);客户端收到ACK进入FIN_WAIT_2。 - 第三次挥手:服务端数据发送完毕,发送
FIN=1,seq=n,进入LAST_ACK。 - 第四次挥手:客户端回复
ACK=1,ack=n+1,进入TIME_WAIT;等待2MSL时间后,客户端彻底close。服务端收到ACK立刻CLOSED。
TCP连接和断开过程中,为什么ACK报文回复的时候需要将刚刚接收接收到的报文中的序列号+1通过ACK报文传送给对方?
这个序列号+1在下一次传输的时候,组要作为下一次TCP传输的第一个序列号,给其标号。
注意:FIN只代表“我不再发送”,不等于不再接收;第二次的ACK和第三次的FIN可以合并,如果服务端没有剩余数据,就变成看上去像三次挥手。
图片详解:
TIME_WAIT状态
主动关闭连接那一方才会出现TIME_WAIT,等待2MSL。
MSL:报文最大生存时间,IP报文在网络最大存活时间
为什么等待2MSL?
- 保证我方最后发送的ACK报文能到达对方;如果ACK丢失,对方会重传FIN,我方TIME_WAIT状态还可以回复ACK。
- 等待网络上本连接残留的旧数据包全部消失,避免新复用端口的连接收到旧连接残留数据。
大量TIME_WAIT怎么处理? TIME_WAIT出现在主动关闭方。出现这个问题可能是我们的电脑上存在爬虫程序。
解决办法:
- 开启地址端口重用(setsockopt,SO_REUSEADDR);
- 内核调小tcp_tw_wait等待时间。
大量CLOSE_WAIT是什么原因?CLOSE_WAIT是被动关闭方,收到对方FIN,回复ACK,但是应用层没有调用close关闭自己socket。
原因:代码忘记close套接字,线程阻塞卡住,没有执行关闭,大量堆积CLOSE_WAIT,耗尽文件描述符。解决方案:代码保证及时close(fd)。
被动关闭方,收到Fin报文并回复ACK报文后会进入close_wait状态,等待上层调用close(fd),来关闭连接TCP连接。
如果出现大量的close_wait状态就说明此时可能存在很多程序作为被动关闭连接的一方,在程序结束前没有调用close()。
解决办法:及时调用close即可。
连接断开在程序中的表现:
recv取完所有的数据后,返回0,不再阻塞。
send触发异常SIG_PIPE,让程序直接退出,为了避免让程序直接退出可以忽略该信号。
5. TCP可靠性实现四大机制
- 序列号 + ACK确认应答:每个字节编号,接收方回复确认号,确认已经收到哪些字节。
- 超时重传:发送报文之后启动计时器,超时没收到ACK,重新发送报文。
- 校验和:校验报文损坏,损坏报文直接丢弃等待重传。
- 去重:收到重复序列号报文直接丢弃。
6. TCP流量控制 vs 拥塞控制
- 流量控制(滑动接收窗口):控制发送方不要超过接收端缓冲区能力,解决收发两端速率不匹配。窗口字段来自接收方返回报文。
- 拥塞控制(拥塞窗口 cwnd):控制发送方不要超过网络链路承载能力,防止网络拥堵,内核算法动态计算。
发送窗口 = min(接收窗口rwnd,拥塞窗口cwnd)
7. TCP适用场景
文件传输、网页HTTP、数据库访问,需要数据完整准确,可接受一定延迟。
三、UDP vs TCP 对比表
| 对比项 | UDP | TCP |
|---|---|---|
| 连接 | 无连接 | 面向连接,三次握手、四次挥手 |
| 可靠性 | 不可靠,无确认重传 | 可靠传输,ack、重传、去重 |
| 传输模式 | 面向数据报,消息有边界,无粘包 | 面向字节流,无消息边界,会粘包 |
| 头部大小 | 固定8字节,开销小 | 20‑60字节,头部更大 |
| 控制机制 | 无流量、拥塞控制 | 滑动窗口流量控制 + 拥塞控制 |
| 实时性 | 高,延迟低 | 相对高延迟 |
| 广播组播 | 支持 | 不支持 |
| 适用场景 | 直播、语音、DNS | HTTP、文件下载、数据库 |
四、重点
为什么UDP不可靠还要用?
TCP为可靠性带来握手、重传、拥塞控制开销;实时业务宁愿丢少量包,也不要等待重传造成卡顿,比如视频通话,丢几帧画面可以接受,卡顿无法接受。
什么是半关闭?
调用shutdown(fd,SHUT_WR),本方不再发送数据,但是还可以接收对方数据,对应四次挥手前半段。
- RST复位报文什么时候出现? 访问一个没有监听的端口;socket已经关闭还收到数据;异常强制断开连接。
- MSS 最大分段大小 TCP选项,每个报文应用层数据最大字节,不包含TCP+IP头部,协商MSS避免IP分片。