【网络】TCP/IP协议深度解析:从连接建立到数据传输
前言
TCP/IP协议栈是互联网的基础,几乎所有的网络应用都建立在TCP/IP之上。深入理解TCP/IP协议的工作原理,对于网络编程、网络故障排查、系统性能优化都是必不可少的知识。作为AI程序员,我们经常需要处理分布式计算、API调用、模型部署等网络相关任务,对TCP/IP协议的深入理解能帮助我们更好地设计和优化这些系统。
本文将从TCP/IP协议栈的整体架构出发,深入讲解TCP协议的三次握手、四次挥手、可靠传输机制、拥塞控制算法等核心内容,并通过大量的图示和实战代码帮助读者建立对TCP/IP协议的深刻理解。
一、TCP/IP协议栈概述
1.1 协议栈的分层结构
TCP/IP协议栈采用分层设计,主要分为四层:链路层、网络层、传输层和应用层。
┌────────────────────────────────────────────┐
│ 应用层 (Application) │
│ HTTP, FTP, SMTP, DNS, SSH, WebSocket │
├────────────────────────────────────────────┤
│ 传输层 (Transport) │
│ TCP, UDP, QUIC │
├────────────────────────────────────────────┤
│ 网络层 (Internet) │
│ IP, ICMP, ARP, BGP │
├────────────────────────────────────────────┤
│ 链路层 (Link) │
│ Ethernet, WiFi, PPP, MPLS │
└────────────────────────────────────────────┘
各层的职责:
- 应用层:负责应用程序间的通信,处理高层协议、数据格式等
- 传输层:提供端到端的通信服务,负责进程间的数据传输
- 网络层:负责主机间的路由和转发,实现不同网络间的互联
- 链路层:负责相邻节点间的数据传输,处理物理地址寻址
1.2 数据封装过程
数据在网络中传输时,每一层都会添加自己的头部信息,这个过程称为封装。
python
# 数据封装示意
# 应用层数据 -> TCP头 -> IP头 -> Ethernet头 -> 物理传输 -> Ethernet尾 -> IP头 -> TCP头 -> 应用层数据
# 实际抓包观察
# 使用Wireshark或tcpdump抓包可以看到完整的分层结构
# tcpdump示例
# sudo tcpdump -i eth0 -nn port 80 -X
# -i: 指定网卡
# -nn: 不解析域名和端口名
# -X: 以十六进制和ASCII显示数据包内容
1.3 socket编程基础
socket是应用层与传输层之间的编程接口:
python
import socket
import threading
# TCP服务器
def tcp_server():
"""TCP服务器示例"""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket.AF_INET: IPv4
# socket.SOCK_STREAM: TCP
# 设置地址复用选项
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('0.0.0.0', 8080)) # 绑定地址和端口
server_socket.listen(128) # 监听连接
print("Server listening on port 8080...")
while True:
client_socket, client_addr = server_socket.accept()
print(f"Connection from {client_addr}")
# 为每个客户端创建线程处理
thread = threading.Thread(target=handle_client, args=(client_socket, client_addr))
thread.daemon = True
thread.start()
def handle_client(client_socket, client_addr):
"""处理客户端请求"""
try:
while True:
data = client_socket.recv(1024) # 接收数据
if not data:
break
print(f"Received from {client_addr}: {data.decode()}")
# 原样返回
client_socket.sendall(data)
except Exception as e:
print(f"Error handling client {client_addr}: {e}")
finally:
client_socket.close()
print(f"Connection closed: {client_addr}")
# TCP客户端
def tcp_client():
"""TCP客户端示例"""
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 8080)) # 连接服务器
print("Connected to server")
# 发送数据
client_socket.sendall(b"Hello, TCP Server!")
# 接收响应
response = client_socket.recv(1024)
print(f"Received from server: {response.decode()}")
client_socket.close()
# UDP服务器
def udp_server():
"""UDP服务器示例"""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# socket.SOCK_DGRAM: UDP
server_socket.bind(('0.0.0.0', 8080))
print("UDP Server listening on port 8080...")
while True:
data, client_addr = server_socket.recvfrom(1024) # 接收数据和地址
print(f"Received from {client_addr}: {data.decode()}")
# 发送响应
server_socket.sendto(b"Hello, UDP Client!", client_addr)
# UDP客户端
def udp_client():
"""UDP客户端示例"""
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client_socket.sendto(b"Hello, UDP Server!", ('127.0.0.1', 8080))
data, server_addr = client_socket.recvfrom(1024)
print(f"Received from server: {data.decode()}")
client_socket.close()
二、TCP三次握手详解
2.1 握手过程分析
TCP是一种面向连接的协议,在传输数据之前必须先建立连接。三次握手是TCP建立连接的过程:
客户端 服务器
│ │
│ ──────────── SYN, seq=x ────────────────────────>│ 第一次握手
│ │ (客户端发送SYN)
│ │
│ <────────── SYN+ACK, seq=y, ack=x+1 ─────────────│ 第二次握手
│ │ (服务器发送SYN+ACK)
│ │
│ ──────────── ACK, seq=x+1, ack=y+1 ─────────────│ 第三次握手
│ │ (客户端发送ACK)
│ │
│ 建立连接完成 │
│ │
第一次握手 :客户端发送SYN包(SYN=1, seq=x),进入SYN_SENT状态
第二次握手 :服务器收到SYN后,发送SYN+ACK包(SYN=1, ACK=1, seq=y, ack=x+1),进入SYN_RCVD状态
第三次握手:客户端收到SYN+ACK后,发送ACK包(ACK=1, seq=x+1, ack=y+1),进入ESTABLISHED状态
python
# 使用socket理解三次握手
import socket
# 当调用listen时,服务器进入LISTEN状态
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8080))
server.listen(128)
# 此时,内核会创建两个队列:
# 1. 半连接队列(SYN队列):存放SYN_RECV状态的连接
# 2. 全连接队列(accept队列):已完成三次握手的连接
# accept从全连接队列取出连接
client, addr = server.accept()
2.2 半连接队列与全连接队列
python
# 查看服务器状态的示例
import socket
import time
def tcp_server_status():
"""演示队列状态"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 9999))
server.listen(5) # listen的backlog参数影响accept队列大小
print("Server listening...")
# 如果不accept,客户端connect会怎样?
# 客户端connect会成功(因为内核完成了三次握手)
# 但服务端进程还没有处理这个连接
# 模拟:客户端快速发送大量连接
# 服务端accept不及时,导致队列溢出
2.3 ISN与序列号
ISN(Initial Sequence Number)是TCP连接的初始序列号,是一个随机生成的值。
python
# 序列号的作用
# 1. 保证数据传输的可靠性
# 2. 保证数据按序接收
# 3. 防止历史报文被重复接收
# 序列号计算
# ISN是随机生成的(现代系统使用半随机算法)
# 后续序列号 = ISN + 已传输的字节数
# 示例:序列号变化
# 假设ISN=1000,发送100字节数据
# 发送100字节后,下一个序列号是1100
# 如果需要重传,序列号仍然从1000开始
# Wireshark中查看序列号
# Relative Sequence Number: 相对序列号(从0开始,方便阅读)
# Absolute Sequence Number: 绝对序列号(真实的ISN)
2.4 三次握手的意义
为什么是三次?不是两次或四次?
python
# 如果是两次握手:
# 客户端发送SYN,服务器返回ACK
# 问题:如果ACK丢失,客户端不知道服务器是否收到了SYN
# 客户端认为连接建立成功,但服务器不知道
# 客户端开始发送数据,但服务器没有建立连接,会导致错误
# 如果是四次握手:
# 技术上可以,但没必要
# 第二次握手可以拆分成SYN+ACK,分开发送
# 但这样会降低效率,而三次已经足够可靠
# 三次握手解决的问题:
# 1. 双方都能确认对方的发送和接收能力正常
# 2. 双方都能协商确定初始序列号
# 3. 避免历史连接初始化混乱
三、TCP四次挥手详解
3.1 挥手过程分析
TCP关闭连接需要四次挥手:
客户端 服务器
│ │
│ ──────────── FIN, seq=u ────────────────────────│ 第一次挥手
│ │ (客户端发送FIN)
│ <─────────── ACK, ack=u+1 ───────────────────────│ 第二次挥手
│ │ (服务器发送ACK)
│ │ (此时客户端->服务器方向关闭)
│ │
│ 关闭半连接 │
│ │
│ <─────────── FIN, seq=w ────────────────────────│ 第三次挥手
│ │ (服务器发送FIN)
│ ──────────── ACK, ack=w+1 ───────────────────────│ 第四次挥手
│ │ (客户端发送ACK)
│ │
│ 关闭完成 │
│ │
│ 客户端等待2MSL后关闭 │
│ │
python
# socket关闭连接
# 方式1:close()
# 立即关闭连接,发送FIN,进入TIME_WAIT状态
# 方式2:shutdown()
# 优雅关闭,可以只关闭一个方向
# shutdown(SHUT_RD): 关闭读
# shutdown(SHUT_WR): 关闭写
# shutdown(SHUT_RDWR): 关闭读写
def graceful_close():
"""优雅关闭连接"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8080))
server.listen(128)
client, addr = server.accept()
# 接收完数据后,优雅关闭写端
data = client.recv(1024)
print(f"Received: {data}")
# 发送响应
client.sendall(b"OK")
# 关闭写端,告诉对方数据已发送完毕
client.shutdown(socket.SHUT_WR)
# 可以继续接收剩余数据
try:
remaining = client.recv(1024)
print(f"Remaining: {remaining}")
except:
pass
client.close()
3.2 TIME_WAIT状态
TIME_WAIT状态是TCP连接关闭后,本地端等待的状态。
python
# TIME_WAIT存在的原因:
# 1. 保证最后的ACK能到达对方(如果ACK丢失,对端会重传FIN)
# 2. 让旧连接的报文在网络中完全消失,避免影响新连接
# TIME_WAIT持续时间:2MSL(Maximum Segment Lifetime)
# MSL是报文在网络中的最大生存时间,通常为30秒或60秒
# 2MSL = 60-120秒
# 问题:服务器在高并发下可能产生大量TIME_WAIT状态的连接
# 解决方案:
# 1. 客户端主动关闭连接(让客户端进入TIME_WAIT)
# 2. 设置socket选项SO_LINGER
# 3. 开启TCP_TIMEWAIT_LEN(Linux内核参数)
# 4. 使用连接池复用连接
# 优化TIME_WAIT
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# SO_REUSEADDR允许绑定到TIME_WAIT状态的端口
3.3 连接状态转换
python
# TCP连接状态
# LISTEN: 服务器等待连接
# SYN_SENT: 客户端已发送SYN
# SYN_RECV: 收到SYN并发送SYN+ACK
# ESTABLISHED: 连接建立成功
# FIN_WAIT_1: 已发送FIN
# FIN_WAIT_2: 收到ACK,等待对方FIN
# CLOSING: 双方同时关闭
# TIME_WAIT: 等待2MSL
# CLOSE: 无连接
# 查看连接状态
# Linux: netstat -an | grep ESTABLISHED
# 或: ss -tan state ESTABLISHED
# Python获取连接状态
import socket
def get_socket_state(sock):
"""获取socket状态"""
# 可以通过/proc/net/tcp查看
pass
四、TCP可靠传输机制
4.1 确认与重传
TCP通过确认(ACK)和重传机制保证可靠传输:
python
# TCP可靠传输原理
# 1. 发送方发送数据,等待ACK
# 2. 接收方收到数据,发送ACK确认
# 3. 如果发送方在超时时间内没收到ACK,重传数据
# 超时时间计算
# TCP使用自适应算法计算RTT(Round Trip Time)
# RTO (Retransmission Timeout) = RTT + 4 * RTTVAR
# RTTVAR是RTT的偏差加权平均值
# 重传机制
# 1. 超时重传:超过RTO没收到ACK就重传
# 2. 快速重传:当收到3个重复ACK时,说明报文丢失,触发快速重传
# 示例:快速重传
# 发送方发送 seq=1000, 2000, 3000, 4000
# 接收方收到 seq=1000, 2000, 3000(seq=4000丢失)
# 接收方发送 ACK=3000(重复ACK)
# 发送方收到3个重复ACK,立即重传 seq=4000
4.2 流量控制
TCP使用滑动窗口实现流量控制:
python
# 滑动窗口原理
# 发送方维护一个发送窗口
# 窗口内的数据可以连续发送,无需等待ACK
# 窗口大小由接收方的接收能力决定
# 窗口大小字段
# TCP头部有16位的Window Size字段
# 最大窗口大小 = 65535字节
# 使用Window Scaling选项可扩展到1GB
# 零窗口
# 当接收方缓冲区满时,Window Size=0
# 发送方停止发送数据
# 接收方处理完后,发送Window Update
# 探测零窗口
# 发送方定期发送零窗口探测(1字节数据)
# 接收方回应当前的窗口大小
4.3 拥塞控制
TCP拥塞控制是保证网络稳定运行的关键机制:
python
# TCP拥塞控制四个算法
# 1. 慢启动(Slow Start)
# 2. 拥塞避免(Congestion Avoidance)
# 3. 快速重传(Fast Retransmit)
# 4. 快速恢复(Fast Recovery)
# 慢启动
# cwnd(拥塞窗口)从1个MSS开始
# 每收到一个ACK,cwnd增加1个MSS
# 指数增长,直到达到ssthresh
# 拥塞避免
# cwnd达到ssthresh后
# 每收到一个ACK,cwnd增加 (MSS * MSS) / cwnd
# 线性增长
# 快速重传与快速恢复
# 收到3个重复ACK时,触发快速重传
# ssthresh = cwnd / 2
# cwnd = ssthresh + 3 * MSS
# 快速重传丢失的包,然后进入快速恢复
# CUBIC算法(Linux默认)
# 使用三次函数调整cwnd
# 更适应高带宽高延迟网络
# 查看TCP拥塞控制算法
# cat /proc/sys/net/ipv4/tcp_congestion_control
# 调整拥塞控制算法
# sysctl -w net.ipv4.tcp_congestion_control=bic
# sysctl -w net.ipv4.tcp_congestion_control=cubic
4.4 Nagle算法与延迟确认
python
# Nagle算法
# 目的:减少小包发送,提高网络效率
# 规则:只有收到前一个数据的ACK,才发送新数据
# 适用于:Telnet等交互式应用
# 问题:可能导致延迟
# 禁用Nagle算法
import socket
tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
# TCP_NODELAY=1: 禁用Nagle算法
# 延迟确认(Delayed ACK)
# 收到数据后,不立即发送ACK
# 等待一段时间,或等到有数据发送时一起发
# 减少ACK数量
# 相关参数
# /proc/sys/net/ipv4/tcp_delack_min
# /proc/sys/net/ipv4/tcp_delayed_ack
五、TCP高级特性
5.1 TCP选项
python
# TCP头部可选字段
# MSS (Maximum Segment Size): 最大报文段大小
# Window Scaling: 窗口扩大因子
# SACK Permitted: 选择性确认
# Timestamps: 时间戳
# PCP (Partial Order Connection Profile): 部分有序连接
# UTO (User Timeout): 用户超时
# 设置MSS
# 在建立连接时协商
# 通常MTU - 40 = MSS
# 以太网MTU=1500, MSS=1460
# 启用SACK
# 允许接收方只确认丢失的数据段
# 提高重传效率
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Linux默认启用SACK
5.2 keepalive
python
# TCP keepalive
# 目的:检测连接是否存活
# 机制:长时间没有数据传输时,发送探测包
# 三个参数(Linux)
# tcp_keepalive_time: 多久没数据传输开始探测(默认7200秒)
# tcp_keepalive_probes: 探测次数(默认9次)
# tcp_keepalive_intvl: 探测间隔(默认75秒)
# 设置keepalive
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# 设置探测参数
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
# 应用层心跳
# 相比keepalive,应用层心跳更可控
def heartbeat_check(sock, interval=30):
"""应用层心跳检测"""
import time
while True:
try:
sock.send(b"PING") # 发送心跳
data = sock.recv(1024)
if data != b"PONG":
# 处理异常
pass
except:
# 连接断开
break
time.sleep(interval)
5.3 多路复用
python
# HTTP/1.1的keepalive
# 一个TCP连接可以发送多个HTTP请求
# 但需要串行等待响应(队头阻塞)
# HTTP/2的多路复用
# 在一个TCP连接上并行发送多个请求/响应
# 解决队头阻塞问题
# WebSocket
# 建立TCP连接后,升级到WebSocket协议
# 全双工通信,可随时发送数据
# gRPC基于HTTP/2
# 多路复用 + 二进制格式 + 流控
# Python异步IO
import asyncio
import socket
async def async_tcp_client():
"""异步TCP客户端"""
reader, writer = await asyncio.open_connection('127.0.0.1', 8080)
writer.write(b"Hello")
await writer.drain() # 等待数据发送完成
data = await reader.read(1024)
print(f"Received: {data}")
writer.close()
await writer.wait_closed()
六、网络抓包与分析
6.1 tcpdump使用
bash
# 基本用法
sudo tcpdump -i eth0 port 80 # 抓取80端口的包
sudo tcpdump -i eth0 host 192.168.1.1 # 抓取与特定IP相关的包
sudo tcpdump -i eth0 -n port 8080 # 不解析域名和端口名
# 保存到文件
sudo tcpdump -i eth0 -w capture.pcap port 80
# -w: 保存到文件
# .pcap文件可以用Wireshark分析
# 读取文件
tcpdump -r capture.pcap
# 常用选项
# -i: 指定网卡
# -n: 不解析域名
# -nn: 不解析域名和端口
# -v: 详细输出(-vv更详细)
# -c: 捕获指定数量的包
# -X: 以十六进制和ASCII显示
# -A: 只显示ASCII
# -e: 显示以太网头部
# -t: 不显示时间戳
# 表达式
# port 80: 端口80
# host 192.168.1.1: 特定IP
# tcp: 只TCP
# udp: 只UDP
# icmp: 只ICMP
# and/or/not: 逻辑组合
# 示例
sudo tcpdump -i eth0 -nn -vv 'tcp port 80 and (host 192.168.1.1 or host 192.168.1.2)'
6.2 Wireshark使用
bash
# Wireshark是图形化的网络协议分析器
# 安装:sudo apt install wireshark
# 常用过滤器
# 协议过滤器: tcp, udp, http, dns, icmp
# 端口过滤器: tcp.port == 80, udp.port == 53
# IP过滤器: ip.addr == 192.168.1.1, ip.src == x.x.x.x
# 逻辑组合: and, or, not
# TCP追踪
# 右键 -> Follow -> TCP Stream
# 可以看到完整的TCP会话
# 过滤器示例
# http.request.method == "GET" # GET请求
# http.response.code == 200 # 200响应
# tcp.analysis.retransmission # 重传包
# tcp.analysis.lost_segment # 丢失的段
6.3 Python网络编程实战
python
import socket
import select
import struct
def create_tcp_packet(src_port, dst_port, seq, ack, flags, data=b""):
"""创建TCP数据包"""
# TCP头部格式
# src_port(2) + dst_port(2) + seq(4) + ack(4) + offset_flags(2) + window(2) + checksum(2) + urgent(2)
src_port = struct.pack('!H', src_port)
dst_port = struct.pack('!H', dst_port)
seq_num = struct.pack('!I', seq)
ack_num = struct.pack('!I', ack)
# 偏移量(5 * 4 = 20字节 = 5个32位字)和标志
offset = (5 << 4) | 0
flags_byte = flags
offset_flags = struct.pack('!H', (offset << 8) | flags_byte)
window = struct.pack('!H', 65535) # 窗口大小
checksum = struct.pack('!H', 0) # 校验和(稍后计算)
urgent = struct.pack('!H', 0)
header = src_port + dst_port + seq_num + ack_num + offset_flags + window + checksum + urgent
return header + data
def parse_tcp_packet(packet):
"""解析TCP数据包"""
if len(packet) < 20:
return None
src_port, dst_port = struct.unpack('!HH', packet[0:4])
seq, ack = struct.unpack('!II', packet[4:12])
offset_flags = struct.unpack('!H', packet[12:14])[0]
offset = (offset_flags >> 8) >> 4
flags = offset_flags & 0xFF
# 提取标志位
fin = flags & 0x01
syn = (flags >> 1) & 0x01
rst = (flags >> 2) & 0x01
psh = (flags >> 3) & 0x01
ack = (flags >> 4) & 0x01
ece = (flags >> 5) & 0x01
urg = (flags >> 6) & 0x01
return {
'src_port': src_port,
'dst_port': dst_port,
'seq': seq,
'ack': ack,
'flags': {
'FIN': fin,
'SYN': syn,
'RST': rst,
'PSH': psh,
'ACK': ack,
'ECE': ece,
'URG': urg
},
'data': packet[offset * 4:]
}
def raw_tcp_client():
"""原始TCP客户端(需要root权限)"""
# 创建原始套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
# socket.IPPROTO_RAW: 包含IP头部
# socket.IPPROTO_TCP: 包含TCP头部
# 设置IP选项
sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# 目标地址
dst_ip = '93.184.216.34' # example.com
dst_port = 80
# 构造数据包
src_port = 12345
seq = 0
ack = 0
flags = 0x02 # SYN
packet = create_tcp_packet(src_port, dst_port, seq, ack, flags)
# 发送
sock.sendto(packet, (dst_ip, 0))
# 接收响应
sock.settimeout(5)
try:
response, addr = sock.recvfrom(65535)
print(f"Received from {addr}")
tcp_info = parse_tcp_packet(response[20:]) # 跳过IP头部
print(tcp_info)
except socket.timeout:
print("Timeout")
sock.close()
七、实战案例:构建高性能TCP服务
7.1 多进程TCP服务器
python
import socket
import multiprocessing
import signal
import time
def handle_client(conn, addr):
"""处理客户端连接"""
print(f"Process {multiprocessing.current_process().pid} handling {addr}")
try:
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
except Exception as e:
print(f"Error: {e}")
finally:
conn.close()
def multi_process_server():
"""多进程TCP服务器"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 8000))
server.listen(128)
print(f"Server listening on port 8000 (PID: {multiprocessing.current_process().pid})")
# 忽略子进程终止信号
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
while True:
conn, addr = server.accept()
print(f"New connection from {addr}")
# 创建子进程处理连接
process = multiprocessing.Process(target=handle_client, args=(conn, addr))
process.daemon = True
process.start()
# 父进程关闭conn(子进程已经有副本)
conn.close()
# 测试
if __name__ == '__main__':
multi_process_server()
7.2 单进程并发服务器(IO多路复用)
python
import socket
import selectors
# 使用select实现IO多路复用
def select_server():
"""使用select的IO多路复用服务器"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 8000))
server.listen(128)
# 注册server socket到select
inputs = [server]
outputs = []
message_queues = {}
print("Server listening on port 8000...")
while inputs:
# 等待可读事件
readable, writable, exceptional = select.select(inputs, outputs, inputs)
for sock in readable:
if sock is server:
# 新连接
conn, addr = sock.accept()
print(f"New connection: {addr}")
conn.setblocking(False) # 非阻塞
inputs.append(conn)
message_queues[conn] = []
else:
# 数据可读
try:
data = sock.recv(1024)
if data:
print(f"Received from {sock.getpeername()}: {data}")
# 加入写队列
message_queues[sock].append(data)
if sock not in outputs:
outputs.append(sock)
else:
# 连接关闭
print(f"Connection closed: {sock.getpeername()}")
inputs.remove(sock)
if sock in outputs:
outputs.remove(sock)
del message_queues[sock]
sock.close()
except Exception as e:
print(f"Error: {e}")
inputs.remove(sock)
sock.close()
for sock in writable:
# 发送数据
if sock in message_queues and message_queues[sock]:
data = message_queues[sock].pop(0)
try:
sock.sendall(data)
if not message_queues[sock]:
outputs.remove(sock)
except Exception as e:
print(f"Send error: {e}")
for sock in exceptional:
# 异常处理
print(f"Exception on {sock.getpeername()}")
inputs.remove(sock)
if sock in outputs:
outputs.remove(sock)
sock.close()
del message_queues[sock]
7.3 使用epoll的服务器
python
import socket
import selectors
def epoll_server():
"""使用epoll的高性能服务器(Linux特有)"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 8000))
server.listen(128)
# 创建epoll对象
epoll = selectors.EpollSelector()
# 注册server socket
server.setblocking(False)
epoll.register(server, selectors.EVENT_READ, None)
print("Server listening on port 8000...")
connections = {}
while True:
# 等待事件
events = epoll.select(timeout=1)
for fd, event in events:
sock = connections.get(fd) if fd in connections else None
if sock is None and event & selectors.EVENT_READ:
# server socket可读,表示有新连接
if fd == server.fileno():
conn, addr = server.accept()
print(f"New connection: {addr}")
conn.setblocking(False)
# 注册新连接
connections[conn.fileno()] = conn
epoll.register(conn, selectors.EVENT_READ, None)
continue
if event & selectors.EVENT_READ:
# 可读
try:
data = sock.recv(1024)
if data:
print(f"Received: {data}")
sock.sendall(data)
else:
# 关闭连接
print(f"Connection closed")
epoll.unregister(sock)
sock.close()
del connections[sock.fileno()]
except Exception as e:
print(f"Error: {e}")
epoll.unregister(sock)
sock.close()
del connections[sock.fileno()]
if event & selectors.EVENT_WRITE:
# 可写
pass
# 使用selectors模块(跨平台,自动选择最佳实现)
def modern_select_server():
"""使用selectors模块的服务器"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 8000))
server.listen(128)
selector = selectors.DefaultSelector()
server.setblocking(False)
def accept_handler(sock):
conn, addr = sock.accept()
print(f"New connection: {addr}")
conn.setblocking(False)
selector.register(conn, selectors.EVENT_READ, read_handler)
def read_handler(conn):
data = conn.recv(1024)
if data:
print(f"Received: {data}")
conn.sendall(data)
else:
print("Connection closed")
selector.unregister(conn)
conn.close()
selector.register(server, selectors.EVENT_READ, accept_handler)
print("Server listening on port 8000...")
while True:
events = selector.select(timeout=1)
for key, event in events:
callback = key.data
callback(key.fileobj)
八、总结
TCP/IP协议是网络通信的基础,深入理解其工作原理对于网络编程和系统优化至关重要。本文深入讲解了:
- 协议栈结构:理解四层模型和数据封装过程
- 三次握手:连接的建立过程和状态转换
- 四次挥手:连接关闭过程和TIME_WAIT状态
- 可靠传输:确认重传、流量控制、拥塞控制机制
- 高级特性:TCP选项、keepalive、多路复用
- 抓包分析:使用tcpdump和Wireshark分析网络问题
- 实战应用:构建高性能TCP服务器的多种模式
作为AI程序员,理解TCP/IP协议能帮助我们:
- 更好地设计和优化分布式系统
- 排查网络相关的问题
- 选择合适的通信协议和参数配置
- 理解gRPC、WebSocket等高层协议的底层原理
希望本文能帮助读者建立起对TCP/IP协议的深入理解,为今后的网络编程工作打下坚实基础。