【学习笔记】TCP 与 UDP
TCP(Transmission Control Protocol)与UDP(User Datagram Protocol)是 网络通讯 中最基础也最常用的两种 传输层 协议。
文章目录
- 1. 简介
- 2. OSI 与 TCP/IP 模型中的定位
- 3. 协议原理与关键机制
- 3.1 UDP
- 3.2 TCP
- 5. 实践:Socket 接口示例
- C语言
- Go语言
- Python语言
- 6. 调优
1. 简介
TCP 是1974年由早期的NCP(Network Control Protocol)逐渐演化出来的概念。它在1980年被拆分成了现在常见的TCP与IP协议,形成经典的 TCP/IP 协议簇。后来,UDP 在 RFC 768(互联网工程任务协会,Internet Engineering Task Force, IETF,发布的一个标准文件)中发布,用于减少延迟、适应简单的请求或应答环境,如DNS(域名系统)。
90年代后,TCP 持续优化拥塞控制算法(如Tahoe、Reno、CUBIC),在万兆与云计算场景中处于核心地位。而UDP则在实时音视频、VoIP(基于IP的语音传输,一种语音通话技术)、在线游戏等领域被广泛使用。
2. OSI 与 TCP/IP 模型中的定位
TCP/IP模型将传统的OSI理论模型简化为4层结构,传输层分为TCP/UDP两种机制。
3. 协议原理与关键机制
3.1 UDP
【注】
- 源/目的端口: 标识进程级别的通信端点
- 长度: 头部与数据总长度,最小8字节
- 校验和: 包含伪首部,检验UDP头与数据的完整性
【机制】
- 无握手: 发送即完成,无三次握手
- 无重传: 丢包不重发、无拥塞控制,由上层应用决定策略。
- 场景: 实时音视频、DNS查询等。实时性要求高,应用自有可靠机制或可忽略丢包。
3.2 TCP
【注】
- 序列号 / 确认号: 实现可靠传输的核心,按字节计数流
- Flags: URG,ACK,PSH,RST,SYN,FIN等,控制连接的建立,终止与数据推送。
- 窗口大小: 流量控制的关键,接收方通告自己缓存区的剩余空间
- 选项: 常见有最大报文段MSS、时间戳、窗口缩放等,用于性能优化。
【机制】
-
三次握手(3-way Handshake):
1. 客户端发送SYN,选择输出序列号x
2. 服务端回应 SYN+ACK,确认号 x+1,并发送自己序列号 y
3. 客户端再发 ACK 确认号 y+1,一切就绪 -
四次挥手(4-way Teardown):
1. 发起方发送 FIN。
2. 对方 ACK。
3. 对方再发 FIN。
4. 原方 ACK,等待 TIME-WAIT 结束后真正释放。 -
重传
超时重传(RTO): 基于往返时间(RTT)估算,动态调整超时重传定时器。
快速重传: 连续收到 3 个相同的ACK时,立即重传疑似丢失的报文段,而无需等待超时。 -
拥塞控制算法
1. 慢启动: 指数增长拥塞窗口(cwnd),直到达到阈值
2. 拥塞避免: 加法增大,线性增长cwnd
3. 快速重传与快速恢复: 检测到丢包后,一方面减小阈值,一方面快速恢复到阈值区。
4. CUBIC(linux默认),BBR(Google提出)等
5. 实践:Socket 接口示例
下面以编程语言示例基础的TCP和UDP的服务端与客户端。
C语言
// UDP 客户端示例:初始化并发送
int sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in serv;
memset(&serv, 0, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(9000);
serv.sin_addr.s_addr = inet_addr("127.0.0.1");
char *msg = "Hello UDP";
sendto(sock, msg, strlen(msg), 0, (struct sockaddr*)&serv, sizeof(serv));// TCP 服务器示例:接受连接并回复
int sock = socket(AF_INET, SOCK_STREAM, 0);
bind(sock, (struct sockaddr*)&serv, sizeof(serv));
listen(sock, 5);
int conn = accept(sock, NULL, NULL);
char buf[1024];
int len = recv(conn, buf, sizeof(buf), 0);
send(conn, "Hello TCP", 9, 0);
close(conn);
Go语言
// UDP Echo 服务器
addr, _ := net.ResolveUDPAddr("udp", ":9000")
conn, _ := net.ListenUDP("udp", addr)
buf := make([]byte, 1024)
for {n, remote, _ := conn.ReadFromUDP(buf)conn.WriteToUDP(buf[:n], remote)
}// TCP 客户端
conn, _ := net.Dial("tcp", "localhost:8000")
fmt.Fprintln(conn, "Hello TCP")
response, _ := bufio.NewReader(conn).ReadString('\n')
fmt.Println("Server replied:", response)
Python语言
# UDP 客户端
import socket
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp.sendto(b"Ping", ("localhost", 9000))
msg, _ = udp.recvfrom(1024)
print(msg)# TCP 服务器
import socket
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.bind(("0.0.0.0", 8000))
srv.listen(1)
conn, addr = srv.accept()
data = conn.recv(1024)
conn.sendall(b"Hello TCP")
conn.close()
6. 调优
- TIME_WAIT消耗: 大量短连接会产生大量的TIME_WAIT,建议启动连接复用、长连接或HTTP/2多路复用。
- MTU与分片: 超大UDP报文易被分片丢弃,建议应用层自行分片与重组。
- Nagle算法: TCP默认启用Nagle,合并小包,可能会增加延迟,需要根据场景选择是否禁用。
- 内核缓冲区调整: 高带宽-时延环境下,增大send/recv缓冲区以避免吞吐瓶颈。
- 拥塞算法: Linux默认CUBIC,若要更低时延可使用BBR。