【网络】TCP/IP协议深度解析:从连接建立到数据传输

【网络】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协议是网络通信的基础,深入理解其工作原理对于网络编程和系统优化至关重要。本文深入讲解了:

  1. 协议栈结构:理解四层模型和数据封装过程
  2. 三次握手:连接的建立过程和状态转换
  3. 四次挥手:连接关闭过程和TIME_WAIT状态
  4. 可靠传输:确认重传、流量控制、拥塞控制机制
  5. 高级特性:TCP选项、keepalive、多路复用
  6. 抓包分析:使用tcpdump和Wireshark分析网络问题
  7. 实战应用:构建高性能TCP服务器的多种模式

作为AI程序员,理解TCP/IP协议能帮助我们:

  • 更好地设计和优化分布式系统
  • 排查网络相关的问题
  • 选择合适的通信协议和参数配置
  • 理解gRPC、WebSocket等高层协议的底层原理

希望本文能帮助读者建立起对TCP/IP协议的深入理解,为今后的网络编程工作打下坚实基础。

相关推荐
月诸清酒2 小时前
66-260522 AI 科技日报 (谷歌永久提高Antigravity平台的Gemini使用限额到3倍)
人工智能
龙腾AI白云2 小时前
【无标题】知识图谱:AI的超级大脑
人工智能·知识图谱·tornado
土星云SaturnCloud2 小时前
土星云边缘计算设备的多模态模型部署实操
服务器·人工智能·ai·边缘计算
Rauser Mack3 小时前
编程零基础,半小时用AI做了两个经典小游戏(附完整Prompt和HTML代码)
人工智能·html·prompt
MediaTea3 小时前
DL:卷积神经网络的基本原理与 PyTorch 实现
人工智能·pytorch·深度学习·神经网络·cnn
csdn小瓯3 小时前
前端工程化:React + TypeScript + Tailwind CSS 的组件化实践
开发语言·人工智能·python
蓦然回首却已人去楼空3 小时前
深度学习进阶:自然语言处理|3.4 QA|用 SimpleCBOW 讲清楚 backward 为什么有的 return,有的不 return
人工智能·深度学习·自然语言处理
Zldaisy3d3 小时前
为增材制造“驱动器”中国,注入规模化应用更强动力 | TCT亚洲展专访西门子全球增材制造副总裁
大数据·人工智能·制造
AllData公司负责人3 小时前
亲测丝滑,体验跃迁|AllData通过集成开源项目StreamPark,实时流任务调度更省心!
java·大数据·数据库·人工智能·算法·实时计算·实时开发平台