Python 网络编程实战:从 TCP/UDP 基础到高并发服务器开发

一、网络编程基础:打开网络通信的大门

1.1 网络编程核心概念

网络编程是指编写运行在网络环境中的程序,实现不同设备、不同进程间的数据交换与通信。在 Python 中,网络编程的核心是socket(套接字)技术 ------ 它是网络通信的 "端点",屏蔽了底层 TCP/IP 协议栈的复杂细节,为应用层提供了统一的编程接口。

网络通信的三大核心要素:

  • IP 地址 :网络设备的唯一标识,相当于 "门牌号",用于定位网络中的目标设备。常用 IPv4 格式(如127.0.0.1192.168.1.100),IPv6 则解决了地址枯竭问题。
  • 端口:设备上应用程序的通信标识,范围 0~65535。0~1023 为知名端口(如 HTTP 的 80 端口、HTTPS 的 443 端口),1024~65535 为动态端口,用于程序临时通信。
  • 协议 :通信双方的 "语言规则",决定数据传输的方式、可靠性和效率。核心传输层协议包括TCPUDP,是网络编程的基础。

1.2 五层网络模型

理解网络模型是掌握网络编程的前提,OSI 七层模型过于复杂,实际开发中常用TCP/IP 五层模型

  1. 应用层:定义数据的具体格式,如 HTTP、FTP、SMTP 等协议,对应我们编写的应用程序。
  2. 传输层:负责端到端的数据传输,核心协议为 TCP、UDP,决定数据传输的可靠性和效率。
  3. 网络层:负责路由寻址,将数据包从源设备传输到目标设备,核心协议为 IP。
  4. 数据链路层:负责物理链路的数据传输,如以太网、WiFi 协议。
  5. 物理层:负责数据的物理传输,如网线、光纤、无线电波等硬件。

1.3 Socket 核心原理

Socket 是对 BSD 套接字 API 的封装,是应用层与传输层之间的接口。我们可以将 Socket 理解为 "网络通信的插座":服务端创建 Socket 并绑定 IP、端口,监听连接请求;客户端创建 Socket,主动连接服务端,建立通信通道。

Python 中socket模块提供了 Socket API 的完整实现,核心参数如下:

  • family:地址族,常用AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地进程通信)。
  • type:套接字类型,SOCK_STREAM(TCP 套接字,面向连接)、SOCK_DGRAM(UDP 套接字,无连接)。
  • proto:协议类型,默认 0,一般无需指定。

二、TCP 编程:可靠的面向连接通信

2.1 TCP 协议核心特性

TCP(Transmission Control Protocol,传输控制协议)是面向连接、可靠、有序的字节流协议,其核心特性如下:

  1. 面向连接:通信前必须通过 "三次握手" 建立连接,通信结束后通过 "四次挥手" 断开连接,类似打电话前的拨号和通话结束后的挂断。
  2. 可靠传输 :通过确认应答、超时重传、滑动窗口、拥塞控制等机制,保证数据无丢失、无重复、按序到达。
  3. 字节流模式 :数据无明确边界,发送方的多次send可能被接收方一次recv接收,需手动处理消息边界。
  4. 全双工通信:通信双方可同时发送和接收数据,无单向限制。
TCP 三次握手与四次挥手
  • 三次握手 :建立可靠连接的过程,核心是同步序列号(SEQ)和确认号(ACK):
    1. 客户端发送SYN报文(同步序列号),请求建立连接;
    2. 服务端回复SYN+ACK报文,确认客户端请求并同步自身序列号;
    3. 客户端发送ACK报文,确认服务端响应,连接建立。
  • 四次挥手 :断开连接的过程,因 TCP 连接是全双工的,需双方分别关闭:
    1. 主动方发送FIN报文,请求关闭连接;
    2. 被动方回复ACK报文,确认收到关闭请求;
    3. 被动方发送FIN报文,请求关闭自身连接;
    4. 主动方回复ACK报文,确认关闭,连接断开。

2.2 TCP 编程核心流程

2.2.1 TCP 服务端流程
  1. 创建 Socket 对象:socket.socket(AF_INET, SOCK_STREAM)
  2. 绑定地址:bind((host, port)),绑定本机 IP 和端口;
  3. 监听连接:listen(backlog),设置最大等待连接数;
  4. 接受连接:accept(),阻塞等待客户端连接,返回新的 Socket 对象(用于通信)和客户端地址;
  5. 收发数据:recv(bufsize)接收数据,send(data)/sendall(data)发送数据;
  6. 关闭连接:关闭通信 Socket 和监听 Socket。
2.2.2 TCP 客户端流程
  1. 创建 Socket 对象:socket.socket(AF_INET, SOCK_STREAM)
  2. 连接服务端:connect((host, port)),主动连接服务端地址;
  3. 收发数据:send(data)发送数据,recv(bufsize)接收响应;
  4. 关闭连接:关闭 Socket。

2.3 TCP 编程实战代码

2.3.1 基础 TCP 回显服务(服务端 + 客户端)

服务端代码(tcp_server.py):实现监听客户端连接,接收数据并原样返回(回显),处理端口复用避免重启报错。

复制代码
# -*- coding: utf-8 -*-
"""
TCP服务端:基础回显服务
功能:监听客户端连接,接收数据并回显,处理多连接基础逻辑
"""
import socket
import sys

def tcp_echo_server(host: str = "0.0.0.0", port: int = 8888, buffer_size: int = 1024):
    """
    TCP回显服务端
    :param host: 监听地址,0.0.0.0表示监听所有网卡
    :param port: 监听端口
    :param buffer_size: 接收数据缓冲区大小
    """
    # 1. 创建TCP套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2. 设置端口复用,避免重启时端口占用报错
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 3. 绑定地址和端口
    try:
        server_socket.bind((host, port))
    except socket.error as e:
        print(f"绑定地址失败:{e}")
        sys.exit(1)
    # 4. 监听连接,backlog=5表示最大等待连接数
    server_socket.listen(5)
    print(f"TCP回显服务启动,监听地址:{host}:{port}")
    print(f"缓冲区大小:{buffer_size}字节,等待客户端连接...")

    try:
        while True:
            # 5. 阻塞等待客户端连接,返回新的通信套接字和客户端地址
            client_socket, client_addr = server_socket.accept()
            print(f"\n新客户端连接:{client_addr[0]}:{client_addr[1]}")
            with client_socket:  # 使用with自动关闭客户端套接字
                while True:
                    # 6. 接收客户端数据,缓冲区大小为buffer_size
                    recv_data = client_socket.recv(buffer_size)
                    if not recv_data:  # 客户端断开连接时,recv返回空字节
                        print(f"客户端{client_addr}断开连接")
                        break
                    # 解码数据(默认utf-8),处理编码异常
                    try:
                        recv_text = recv_data.decode("utf-8")
                    except UnicodeDecodeError:
                        print(f"接收数据编码错误,原始数据:{recv_data}")
                        recv_text = str(recv_data)
                    print(f"收到{client_addr}的数据:{recv_text}")
                    # 7. 回显数据:编码后发送
                    send_data = f"服务端回显:{recv_text}".encode("utf-8")
                    client_socket.sendall(send_data)
    except KeyboardInterrupt:
        print("\n服务端手动停止,关闭套接字...")
    finally:
        # 8. 关闭监听套接字
        server_socket.close()
        print("服务端套接字已关闭")

if __name__ == "__main__":
    # 可通过命令行参数指定端口:python tcp_server.py 9999
    if len(sys.argv) > 1:
        tcp_echo_server(port=int(sys.argv[1]))
    else:
        tcp_echo_server()

客户端代码(tcp_client.py):主动连接服务端,发送用户输入的消息,接收回显数据。

复制代码
# -*- coding: utf-8 -*-
"""
TCP客户端:基础回显客户端
功能:连接TCP服务端,发送消息并接收回显
"""
import socket
import sys

def tcp_echo_client(host: str = "127.0.0.1", port: int = 8888, buffer_size: int = 1024):
    """
    TCP回显客户端
    :param host: 服务端IP地址
    :param port: 服务端端口
    :param buffer_size: 接收缓冲区大小
    """
    # 1. 创建TCP套接字
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        # 2. 连接服务端
        client_socket.connect((host, port))
        print(f"已连接到TCP服务端:{host}:{port}")
        print("输入消息发送(输入exit退出):")
        while True:
            # 3. 获取用户输入
            send_text = input("你说:")
            if send_text.lower() == "exit":
                print("客户端退出连接...")
                break
            if not send_text:
                print("请输入有效内容")
                continue
            # 4. 编码后发送数据
            send_data = send_text.encode("utf-8")
            client_socket.sendall(send_data)
            # 5. 接收服务端回显数据
            recv_data = client_socket.recv(buffer_size)
            if not recv_data:
                print("服务端断开连接")
                break
            # 解码并打印回显内容
            recv_text = recv_data.decode("utf-8")
            print(f"服务端说:{recv_text}")
    except socket.error as e:
        print(f"连接服务端失败:{e}")
    except KeyboardInterrupt:
        print("\n客户端手动退出...")
    finally:
        # 6. 关闭套接字
        client_socket.close()
        print("客户端套接字已关闭")

if __name__ == "__main__":
    if len(sys.argv) > 1:
        tcp_echo_client(port=int(sys.argv[1]))
    else:
        tcp_echo_client()
2.3.2 进阶:处理粘包问题

TCP 是字节流协议,无消息边界,连续发送的小数据会被合并,导致粘包问题 。例如:客户端连续发送"Hello""World",服务端可能一次接收"HelloWorld",无法区分两条消息。

解决方案

  1. 固定长度:每条消息固定长度,不足补空格 / 0;
  2. 特殊分隔符 :用特殊字符(如\r\n\r\n)分隔消息,需保证消息体不含分隔符;
  3. 长度前缀法:在消息前添加 4 字节的长度标识,指定消息体长度(最常用)。

长度前缀法实战代码

复制代码
# -*- coding: utf-8 -*-
"""
TCP粘包处理:长度前缀法
核心:发送前添加4字节消息长度,接收时先读长度,再读对应长度数据
"""
import socket
import struct

# 发送函数:处理粘包
def send_msg(sock: socket.socket, data: bytes):
    """
    发送带长度前缀的消息
    :param sock: 通信套接字
    :param data: 要发送的字节数据
    """
    # 1. 计算消息长度,打包为4字节大端整数
    data_len = len(data)
    len_pack = struct.pack("!I", data_len)  # !I表示大端无符号整数
    # 2. 先发送长度,再发送数据
    sock.sendall(len_pack + data)

# 接收函数:处理粘包
def recv_msg(sock: socket.socket, buffer_size: int = 1024) -> bytes | None:
    """
    接收带长度前缀的消息
    :param sock: 通信套接字
    :param buffer_size: 缓冲区大小
    :return: 接收的字节数据,None表示连接断开
    """
    # 1. 先接收4字节长度
    len_pack = sock.recv(4)
    if not len_pack:
        return None
    # 2. 解包获取消息长度
    data_len = struct.unpack("!I", len_pack)[0]
    # 3. 循环接收指定长度的数据
    recv_data = b""
    while len(recv_data) < data_len:
        chunk = sock.recv(min(buffer_size, data_len - len(recv_data)))
        if not chunk:
            return None
        recv_data += chunk
    return recv_data

# 改进后的服务端(处理粘包)
def tcp_sticky_server(host: str = "0.0.0.0", port: int = 8889):
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((host, port))
    server_socket.listen(5)
    print(f"粘包处理服务端启动:{host}:{port}")

    try:
        while True:
            client_socket, client_addr = server_socket.accept()
            print(f"\n新客户端连接:{client_addr}")
            with client_socket:
                while True:
                    # 接收粘包处理后的消息
                    recv_data = recv_msg(client_socket)
                    if not recv_data:
                        print(f"客户端{client_addr}断开连接")
                        break
                    recv_text = recv_data.decode("utf-8")
                    print(f"收到{client_addr}的消息:{recv_text}")
                    # 回显消息
                    send_data = f"粘包处理回显:{recv_text}".encode("utf-8")
                    send_msg(client_socket, send_data)
    except KeyboardInterrupt:
        print("\n服务端停止")
    finally:
        server_socket.close()

# 改进后的客户端(处理粘包)
def tcp_sticky_client(host: str = "127.0.0.1", port: int = 8889):
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect((host, port))
    print(f"已连接到服务端:{host}:{port}")

    try:
        # 模拟连续发送多条消息,测试粘包处理
        messages = ["第一条消息", "第二条稍长一点的消息测试", "第三条消息ABCDEFG"]
        for msg in messages:
            send_data = msg.encode("utf-8")
            send_msg(client_socket, send_data)
            print(f"发送消息:{msg}")
            # 接收回显
            recv_data = recv_msg(client_socket)
            if recv_data:
                print(f"服务端回显:{recv_data.decode('utf-8')}")
        print("\n测试完成,退出连接...")
    except socket.error as e:
        print(f"通信失败:{e}")
    finally:
        client_socket.close()

if __name__ == "__main__":
    # 启动服务端:python tcp_sticky_server.py
    # 启动客户端:python tcp_sticky_client.py
    import sys
    if sys.argv[0].endswith("tcp_sticky_server.py"):
        tcp_sticky_server()
    else:
        tcp_sticky_client()

2.4 TCP 编程常见问题与解决方案

表格

问题 原因 解决方案
端口占用报错 Socket 关闭后进入 TIME_WAIT 状态,重启服务端无法绑定端口 设置SO_REUSEADDR选项:sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
接收数据为空 客户端正常断开连接,或网络异常 循环接收时判断if not recv_data,处理断开逻辑
发送数据失败
相关推荐
乾元2 小时前
《硅基之盾》番外篇二:算力底座的暗战——智算中心 VXLAN/EVPN 架构下的多租户隔离与防御
网络·人工智能·网络安全·架构
智擎软件测评小祺2 小时前
渗透测试报告关键模块拆解
网络·web安全·渗透测试·测试·检测·cma·cnas
weixin_513449962 小时前
walk_these_ways项目学习记录第八篇(通过行为多样性 (MoB) 实现地形泛化)--策略网络
开发语言·人工智能·python·学习
飞Link2 小时前
逆向兼容的桥梁:3to2 自动化降级工具实现全解析
运维·开发语言·python·自动化
曾阿伦2 小时前
Python3 文件 (夹) 操作备忘录
开发语言·python
W.W.H.2 小时前
嵌入式常见的面试题1
linux·网络·经验分享·网络协议·tcp/ip
zmj3203242 小时前
CAN + 以太网 + Wi-Fi + BLE + TCP/IP + MQTT +HTTP协议层级
网络·网络协议·tcp/ip
HXQ_晴天3 小时前
Linux 系统的交互式进程监控工具htop
linux·服务器·网络
架构师老Y3 小时前
006、异步编程与并发模型:asyncio与高性能后端
python