欢迎光临
我们一直在努力

Python 进阶提高:Python 网络编程进阶实战指南

摘要:本文从 TCP/UDP 基础通信讲起,带你逐步掌握多线程、select 模型与 asyncio 异步编程,并深入自定义应用层协议设计、常见异常排查与安全加固策略,最后通过一个多客户端聊天室实战项目串联全部知识点,助你从零构建健壮高效的 Python 网络应用。

(快速阅读)

  • TCP/UDP 选型:重可靠选 TCP,重低延迟选 UDP。
  • 并发演进路径:阻塞 → 多线程 → select → asyncio。
  • 粘包解决方案:用「长度前缀 + 数据体」协议切分。
  • 安全加固要点:启用 TLS 加密,限制监听范围。

在实际开发中,网络通信往往是让许多初学者感到头疼的环节。明明代码逻辑看起来没问题,连接却总是超时;或者数据发出去了,对方却收不到完整的包。这些问题背后,其实是对底层 socket 机制理解不够透彻,以及缺乏系统的调试思路。无论是构建微服务之间的内部调用,还是开发物联网设备的上报程序,掌握稳定的网络编程能力都是后端工程师的必修课。

很多教程一上来就丢出一大段异步代码,让人云里雾里。其实,网络编程的核心在于理解“连接”的本质和数据的流动方式。从最基础的 TCP 握手到复杂的并发模型,每一步都有其特定的应用场景和取舍。只有亲手写过阻塞式的服务端,才能体会到为什么需要引入多路复用;只有经历过粘包断包的困扰,才会明白自定义协议的重要性。

这篇文章将带你从零开始,系统地梳理 Python 网络编程的全貌。我们不会只停留在理论概念上,而是会一步步搭建环境,编写可运行的 TCP 和 UDP 示例,逐步演进到多线程、IO 多路复用以及异步编程。同时,还会分享一些在实际项目中积累的异常排查技巧和性能优化策略,帮助你避开那些常见的坑,写出既健壮又高效的网络应用。

目录

  • ① 核心概念解析与 socket 模块初探
  • ② 开发环境搭建与依赖库安装
  • ③ 构建基础 TCP 服务端与客户端
  • ④ 实现 UDP 协议通信与数据收发
  • ⑤ 多线程并发处理连接请求
  • ⑥ 使用 select 模型提升 IO 效率
  • ⑦ 异步编程 asyncio 在网络中的应用
  • ⑧ 自定义应用层协议设计
  • ⑨ 常见连接异常诊断与调试技巧
  • ⑩ 网络安全加固与性能优化策略
  • ⑪ 常见问题 FAQ
  • ⑫ 实战:构建简易聊天室
  • ⑬ 总结与参考资料
  • ⑭ 学习进阶

① 核心概念解析与 socket 模块初探

网络编程的基石是 Socket(套接字),它可以被理解为操作系统提供给应用程序访问网络协议的接口。如果把网络通信比作打电话,那么 IP 地址就是电话号码,端口号则是分机号,而 Socket 就是你手中的听筒和话筒。在 Python 中,socket 模块封装了底层的系统调用,让我们能够用简洁的代码实现复杂的网络交互。

Socket 主要分为两大类:流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。前者对应 TCP 协议,特点是面向连接、可靠传输,数据像水流一样有序到达,适合文件传输、网页浏览等场景;后者对应 UDP 协议,无连接、不可靠但速度快,数据包可能丢失或乱序,常用于视频直播、实时游戏等对延迟敏感的业务。理解这两者的区别,是选择正确通信方案的前提。

在使用 socket 模块时,我们通常需要先导入它,然后创建一个 socket 对象。这个对象包含了协议族(如 IPv4)、类型以及具体的通信方法。虽然现代框架屏蔽了很多细节,但直接操作 socket 能让我们更清晰地看到数据是如何在网络中穿梭的,这对于排查深层问题至关重要。

为了更直观地理解两者的差异,下面用一张表格对比 TCP 与 UDP 在几个关键维度上的表现:

对比维度
TCP
UDP
连接方式 面向连接,需三次握手建立连接 无连接,直接发送数据报
可靠性 可靠传输,有确认、重传机制 不可靠,数据可能丢失或重复
传输顺序 有序到达,保证数据顺序 可能乱序,需应用层处理
速度 相对较慢,有握手和确认开销 速度快,延迟低
典型应用场景 文件传输、网页浏览、邮件 视频直播、实时游戏、DNS 查询

选择依据:如果业务对数据完整性要求极高,且允许一定的延迟(如文件上传、网页加载),TCP 是首选;如果追求极致的低延迟,且能容忍少量丢包(如实时音视频、在线游戏),UDP 则更合适。理解这两者的取舍,是设计网络应用的第一步。

TCP 连接建立与释放过程:TCP 是面向连接的协议,通信前需要通过「三次握手」建立连接,通信结束后通过「四次挥手」释放连接。下面用一张时序图直观展示这两个过程:

服务端

客户端

服务端

客户端

#mermaid-svg-cxDJ04NZ3IxPb5vr{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cxDJ04NZ3IxPb5vr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cxDJ04NZ3IxPb5vr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cxDJ04NZ3IxPb5vr .error-icon{fill:#552222;}#mermaid-svg-cxDJ04NZ3IxPb5vr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cxDJ04NZ3IxPb5vr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cxDJ04NZ3IxPb5vr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cxDJ04NZ3IxPb5vr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cxDJ04NZ3IxPb5vr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cxDJ04NZ3IxPb5vr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cxDJ04NZ3IxPb5vr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cxDJ04NZ3IxPb5vr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cxDJ04NZ3IxPb5vr .marker.cross{stroke:#333333;}#mermaid-svg-cxDJ04NZ3IxPb5vr svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cxDJ04NZ3IxPb5vr p{margin:0;}#mermaid-svg-cxDJ04NZ3IxPb5vr .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cxDJ04NZ3IxPb5vr text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-cxDJ04NZ3IxPb5vr .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cxDJ04NZ3IxPb5vr .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-cxDJ04NZ3IxPb5vr .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-cxDJ04NZ3IxPb5vr .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-cxDJ04NZ3IxPb5vr #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-cxDJ04NZ3IxPb5vr .sequenceNumber{fill:white;}#mermaid-svg-cxDJ04NZ3IxPb5vr #sequencenumber{fill:#333;}#mermaid-svg-cxDJ04NZ3IxPb5vr #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-cxDJ04NZ3IxPb5vr .messageText{fill:#333;stroke:none;}#mermaid-svg-cxDJ04NZ3IxPb5vr .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cxDJ04NZ3IxPb5vr .labelText,#mermaid-svg-cxDJ04NZ3IxPb5vr .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-cxDJ04NZ3IxPb5vr .loopText,#mermaid-svg-cxDJ04NZ3IxPb5vr .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-cxDJ04NZ3IxPb5vr .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cxDJ04NZ3IxPb5vr .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-cxDJ04NZ3IxPb5vr .noteText,#mermaid-svg-cxDJ04NZ3IxPb5vr .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-cxDJ04NZ3IxPb5vr .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cxDJ04NZ3IxPb5vr .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cxDJ04NZ3IxPb5vr .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cxDJ04NZ3IxPb5vr .actorPopupMenu{position:absolute;}#mermaid-svg-cxDJ04NZ3IxPb5vr .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-cxDJ04NZ3IxPb5vr .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cxDJ04NZ3IxPb5vr .actor-man circle,#mermaid-svg-cxDJ04NZ3IxPb5vr line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-cxDJ04NZ3IxPb5vr :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

三次握手(建立连接)

数据传输阶段

四次挥手(释放连接)

SYN(seq=x,请求建立连接)

SYN-ACK(seq=y,ack=x+1,同意连接)

ACK(seq=x+1,ack=y+1,确认收到)

FIN(seq=m,请求关闭连接)

ACK(seq=n,ack=m+1,确认收到关闭请求)

FIN(seq=n,ack=m+1,服务端也关闭连接)

ACK(seq=m+1,ack=n+1,确认关闭)

三次握手的状态变化:

  • 第一次握手(SYN):客户端主动向服务端发送 SYN 报文,携带初始序列号 seq=x,此时客户端进入 SYN_SENT 状态,表示请求建立连接。
  • 第二次握手(SYN-ACK):服务端收到 SYN 后,回复 SYN-ACK 报文,携带自己的序列号 seq=y,并确认客户端的序列号 ack=x+1,此时服务端进入 SYN_RCVD 状态,表示同意建立连接。
  • 第三次握手(ACK):客户端收到 SYN-ACK 后,再发送 ACK 报文确认,携带 seq=x+1、ack=y+1,此时双方进入 ESTABLISHED 状态,连接建立成功,可以开始传输数据。

四次挥手的状态变化:

  • 第一次挥手(FIN):主动关闭方(以客户端为例)发送 FIN 报文,携带 seq=m,表示数据发送完毕,请求关闭连接,客户端进入 FIN_WAIT_1 状态。
  • 第二次挥手(ACK):服务端收到 FIN 后,回复 ACK 报文确认,携带 ack=m+1,表示收到关闭请求,但服务端可能还有数据要发送,此时服务端进入 CLOSE_WAIT 状态,客户端进入 FIN_WAIT_2 状态。
  • 第三次挥手(FIN):服务端数据发送完毕后,也发送 FIN 报文,携带 seq=n,表示服务端也准备关闭连接,服务端进入 LAST_ACK 状态。
  • 第四次挥手(ACK):客户端收到 FIN 后,回复 ACK 报文确认,携带 ack=n+1,随后客户端进入 TIME_WAIT 状态,等待 2MSL(最大报文段生存时间)后彻底关闭;服务端收到 ACK 后立即进入 CLOSED 状态。

为什么是三次握手而不是两次:三次握手能确保双方都确认对方的收发能力正常。如果只有两次握手,服务端无法确认客户端是否收到了自己的 SYN-ACK,可能导致已失效的连接请求突然到达服务端,造成资源浪费。为什么是四次挥手而不是三次:因为 TCP 是全双工通信,客户端发送 FIN 只代表客户端不再发送数据,服务端可能还有数据要发给客户端,所以服务端的 FIN 和 ACK 必须分开发送,不能合并。

② 开发环境搭建与依赖库安装

Python 的网络编程功能主要内置于标准库中,因此大多数情况下无需安装额外的第三方依赖即可开始实验。你只需要确保本地安装了 Python 3.x 版本,并配置好了基本的开发环境。推荐使用虚拟环境(如 venv)来隔离项目依赖,避免污染全局环境。

第一步:检查 Python 版本

打开终端,输入以下命令确认 Python 已正确安装:

# 查看 Python 版本(Linux/macOS 使用 python3,Windows 使用 python 或 py)
python3 –version

# 如果提示找不到命令,先安装 Python 3.8 及以上版本
# macOS 可用 Homebrew 安装:brew install python3
# Ubuntu/Debian 可用 apt 安装:sudo apt install python3
# Windows 请到 python.org 下载安装包,安装时勾选 \”Add Python to PATH\”

建议使用 Python 3.8 及以上版本,因为 asyncio 的 asyncio.run() 等现代 API 在 3.7 之后才趋于稳定,3.8 之后体验更佳。本文所有示例代码均基于 Python 3.8+ 编写。

第二步:创建并激活虚拟环境

虚拟环境能把项目依赖与系统全局环境隔离,避免不同项目之间的包版本冲突,是 Python 工程化的最佳实践:

# 在项目目录下创建虚拟环境(venv 是 Python 3.3+ 内置模块,无需额外安装)
python3 -m venv venv

# 激活虚拟环境
# Linux/macOS:
source venv/bin/activate
# Windows(CMD):
venv\\Scripts\\activate.bat
# Windows(PowerShell):
venv\\Scripts\\Activate.ps1

# 激活成功后,命令行提示符前会出现 (venv) 前缀
# 退出虚拟环境:
deactivate

第三步:安装可选依赖

本文的核心示例全部基于标准库 socket、threading、select、asyncio,无需安装任何第三方包。但为了后续扩展实验,可以按需安装以下工具:

# 抓包分析工具(系统级,非 Python 包)
# macOS:brew install wireshark tcpdump
# Ubuntu/Debian:sudo apt install wireshark tcpdump

# 压测工具(可选,用于模拟高并发连接)
# macOS:brew install stress
# Ubuntu/Debian:sudo apt install stress

# Python 第三方库(可选,进阶阶段使用)
pip install requests aiohttp

第四步:验证环境是否就绪

# 确认 Python 版本
python3 –version

# 确认 socket 模块可用(标准库,无需安装)
python3 -c \”import socket; print(socket.__version__ if hasattr(socket, \’__version__\’) else \’socket 模块就绪\’)\”

# 确认 asyncio 模块可用
python3 -c \”import asyncio; print(\’asyncio 模块就绪\’)\”

如果以上命令都能正常输出,说明开发环境已完全就绪,可以开始编写网络程序了。

关于第三方库的取舍:虽然 requests、aiohttp 等高级库非常流行,但在深入学习阶段,暂时不要依赖它们。直接使用原生的 socket 库虽然代码量稍多,但能让你对字节流、缓冲区、阻塞与非阻塞模式有更深刻的体感。当你能熟练处理原生 socket 后,再使用高级框架往往会事半功倍。

开发工具推荐:日常编写和调试网络程序,推荐使用 VS Code 搭配 Python 插件,或 PyCharm Community 版。两者都内置了终端、调试器和代码补全,能显著提升开发效率。调试网络程序时,善用 IDE 的断点调试功能,配合 print 日志,能更快定位问题。

③ 构建基础 TCP 服务端与客户端

TCP 通信遵循经典的“服务端监听 – 客户端连接”模型。服务端首先需要创建一个 socket,绑定到指定的 IP 和端口,然后进入监听状态。一旦有客户端发起连接请求,服务端通过 accept() 方法建立一个新的连接套接字,专门用于与该客户端通信。

服务端:监听与接受连接

下面是一个极简的 TCP 服务端示例:

import socket

def start_tcp_server():
# 创建 IPv4 TCP 套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 允许端口重用,避免重启时报错
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server_address = (\’127.0.0.1\’, 8888)
server_socket.bind(server_address)
server_socket.listen(5)

print(f\”服务器正在监听 {

server_address}…\”)

while True:
client_socket, client_address = server_socket.accept()
print(f\”新连接来自:{

client_address}\”)

try:
data = client_socket.recv(1024)
if data:
response = f\”收到消息:{

data.decode(\’utf-8\’)}\”
client_socket.sendall(response.encode(\’utf-8\’))
except Exception as e:
print(f\”通信错误:{

e}\”)
finally:
client_socket.close()

if __name__ == \”__main__\”:
start_tcp_server()

服务端关键步骤拆解:

  • 创建套接字:socket.socket(socket.AF_INET, socket.SOCK_STREAM) 创建 IPv4 的 TCP 套接字。AF_INET 表示使用 IPv4 地址族,SOCK_STREAM 表示使用流式套接字(对应 TCP)。
  • 设置端口复用:setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 允许端口重用。如果不加这一行,服务端关闭后立即重启,可能因端口处于 TIME_WAIT 状态而报 Address already in use 错误。
  • 绑定地址:bind((\’127.0.0.1\’, 8888)) 把套接字绑定到本机回环地址的 8888 端口。127.0.0.1 表示只允许本机访问,若需局域网访问可改为 0.0.0.0。
  • 进入监听:listen(5) 让服务端进入监听状态,参数 5 表示等待连接队列的最大长度。超过该长度后,新的连接请求会被拒绝。
  • 接受连接:accept() 会阻塞等待客户端连接,一旦有连接到来,返回一个新的套接字 client_socket 和客户端地址 client_address。注意:accept() 返回的是新套接字,专门用于与该客户端通信,而原来的 server_socket 继续监听新的连接。

客户端:连接与收发数据

客户端的逻辑则相对简单,创建 socket 后直接调用 connect() 连接服务端,随后即可发送和接收数据:

import socket

def start_tcp_client():
# 创建 IPv4 TCP 套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (\’127.0.0.1\’, 8888)

try:
# 连接服务端
client_socket.connect(server_address)
print(f\”已连接到服务器 {

server_address}\”)

# 发送数据
message = \”你好,服务器!\”
client_socket.sendall(message.encode(\’utf-8\’))
print(f\”已发送:{

message}\”)

# 接收服务端回复
response = client_socket.recv(1024)
print(f\”收到回复:{

response.decode(\’utf-8\’)}\”)

except ConnectionRefusedError:
print(\”连接被拒绝,请确认服务端已启动\”)
except socket.timeout:
print(\”连接超时\”)
except Exception as e:
print(f\”通信错误:{

e}\”)
finally:
client_socket.close()

if __name__ == \”__main__\”:
start_tcp_client()

客户端关键步骤拆解:

  • 创建套接字:与服务端相同,创建 IPv4 的 TCP 套接字。
  • 发起连接:connect(server_address) 向服务端发起连接请求,触发 TCP 三次握手。连接成功后即可通信。
  • 发送数据:sendall(message.encode(\’utf-8\’)) 把字符串编码为字节流后发送。sendall() 会确保所有数据都发送完毕,比 send() 更可靠。
  • 接收数据:recv(1024) 接收服务端返回的数据,参数 1024 表示最多读取 1024 字节。注意:recv() 方法默认是阻塞的,如果没有数据到达,程序会停在那里等待。

运行演示

先在一个终端启动服务端,再开另一个终端启动客户端:

# 终端 1:启动服务端
$ python tcp_server.py
服务器正在监听 (\’127.0.0.1\’, 8888)...
新连接来自:(\’127.0.0.1\’, 54321)

# 终端 2:启动客户端
$ python tcp_client.py
已连接到服务器 (\’127.0.0.1\’, 8888)
已发送:你好,服务器!
收到回复:收到消息:你好,服务器!

常见问题与注意事项

  • recv() 是阻塞的:如果没有数据到达,程序会一直停在那里。这种同步模型逻辑清晰,但在处理多个客户端时会显得力不从心——服务端处理完一个客户端后,才能接受下一个连接。这也引出了后续对并发模型的探讨。
  • sendall() 与 send() 的区别:send() 可能只发送部分数据,需要循环调用直到全部发送完毕;sendall() 内部封装了循环逻辑,确保数据全部发送,推荐优先使用。
  • 数据编码:网络传输的是字节流,发送前用 encode(\’utf-8\’) 把字符串转为字节,接收后用 decode(\’utf-8\’) 把字节转回字符串。如果两端编码不一致,会出现乱码。
  • 异常处理:客户端连接时可能遇到 ConnectionRefusedError(服务端未启动)、socket.timeout(网络不通)等异常,务必做好捕获与提示。
  • 粘包与拆包问题:这是 TCP 编程中最经典也最容易踩的坑。TCP 是面向字节流的协议,它本身不关心消息边界——发送方连续发送多条消息时,接收方可能一次性收到合并后的数据(粘包);反之,一条较长的消息也可能被拆成多个数据包到达,接收方一次 recv() 只能读到一部分(拆包/半包)。下面详细分析原因与解决方案。

问题产生原因:

  • 粘包:发送方连续多次调用 sendall() 发送短消息时,TCP 协议栈可能把这些小数据块合并到一个 TCP 段中一次性发送;接收方一次 recv() 就可能读到多条消息的拼接结果,无法区分边界。
  • 拆包:当单条消息长度超过 TCP 缓冲区大小(或超过 MSS 最大报文段长度)时,一条消息会被拆分成多个 TCP 段传输;接收方一次 recv() 只能读到其中一部分,导致数据不完整。
  • 本质:TCP 只保证字节的有序、可靠传输,不保证「一次发送对应一次接收」。应用层必须自己定义消息边界。

解决方案一:固定长度法

发送方把所有消息都补齐到固定长度(不足部分用空格或 \\0 填充),接收方每次按固定长度读取。实现简单,但浪费带宽,适合消息长度相对固定的场景:

import socket

FIXED_SIZE = 1024 # 约定固定消息长度

def send_fixed(sock, msg):
\”\”\”发送端:把消息补齐到固定长度再发送\”\”\”
data = msg.encode(\’utf-8\’)
# 不足固定长度时用空格补齐,超过则截断
padded = data.ljust(FIXED_SIZE, b\’ \’)
sock.sendall(padded)

def recv_fixed(sock):
\”\”\”接收端:精确读取固定长度的字节\”\”\”
chunks = []
remaining = FIXED_SIZE
while remaining > 0:
chunk = sock.recv(remaining)
if not chunk:
raise ConnectionError(\”连接中断\”)
chunks.append(chunk)
remaining -= len(chunk)
# 去掉末尾补齐的空格,还原原始消息
return b\’\’.join(chunks).rstrip(b\’ \’).decode(\’utf-8\’)

解决方案二:长度前缀法(推荐)

发送方在每条消息前加上一个固定长度的头部(通常 4 字节),记录数据体的字节数;接收方先读头部解析出长度,再按长度精确读取数据体。这是目前最通用、最节省带宽的方案:

import socket
import struct

def send_message(sock, msg):
\”\”\”发送端:先发送 4 字节长度头(大端序),再发送数据体\”\”\”
data = msg.encode(\’utf-8\’)
header = struct.pack(\’!I\’, len(data)) # !I 表示大端序无符号整数
sock.sendall(header + data)

def recv_exact(sock, length):
\”\”\”接收端:精确读取指定长度的字节,处理拆包\”\”\”
chunks = []
remaining = length
while remaining > 0:
chunk = sock.recv(remaining)
if not chunk:
raise ConnectionError(\”连接中断\”)
chunks.append(chunk)
remaining -= len(chunk)
return b\’\’.join(chunks)

def recv_message(sock):
\”\”\”接收端:先读 4 字节头部解析长度,再读数据体\”\”\”
header = recv_exact(sock, 4)
msg_length = struct.unpack(\’!I\’, header)[0]
body = recv_exact(sock

赞(0)
未经允许不得转载:171主机测评 » Python 进阶提高:Python 网络编程进阶实战指南
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址