
1. 项目概述为什么从UDP开始学网络编程如果你刚开始接触Python网络编程或者想快速实现一个能接收网络数据的程序那么从UDP协议入手绝对是最高效、最不容易劝退的起点。很多人一上来就啃TCP被“三次握手”、“流量控制”、“粘包拆包”这些概念绕得晕头转向还没写出几行代码热情就先被浇灭了一半。而UDP则不同它就像寄明信片写好内容贴上地址IP和端口扔进邮筒至于对方收没收到你并不关心也不会得到回执。这种“无连接”的特性让它的编程模型变得极其简单。这个项目的核心就是带你用Python的socket库一步步搭建一个可靠的UDP数据接收端。我会从最基础的“创建套接字-绑定端口-接收数据”流程讲起然后深入到实际开发中必然会遇到的坑比如端口被占用怎么办收到乱码数据如何解码如何同时处理多个客户端最后我还会提供一个我自用的UDP测试工具文件和一个封装好的API例程。你拿到手后不仅能立刻测试自己的接收程序还能像搭积木一样把这些代码块用到你的实际项目里比如物联网传感器数据采集、游戏状态同步、日志收集服务器等场景。我之所以花时间整理这个“保姆级”教程是因为我见过太多新手在第一步就被环境配置、概念理解或者一个莫名其妙的错误拦住浪费大量时间。我希望通过这篇内容让你在30分钟内就能看到一个实实在在跑起来的UDP服务端并且理解每一行代码背后的“所以然”。2. 核心原理与工具选型为什么是Python的socket在动手之前我们得先搞清楚两件事UDP协议的本质以及为什么用Python的socket来实现它。这能帮你未来举一反三甚至去理解其他语言的网络编程。2.1 UDP协议的精髓简单与高效你可以把网络通信想象成送信。TCP是挂号信需要签收确认如果没送到邮局操作系统会反复投递保证信件按顺序、不丢失地到达。而UDP是普通平信你投进邮筒后邮局只负责送一次不保证对方一定能收到也不保证先寄的信先到。这种设计带来了两大特点无连接通信前不需要像TCP那样先建立漫长的“握手”连接。发送方直接对着目标地址“喊话”就行。不可靠数据包可能丢失、重复、乱序。应用程序需要自己处理这些情况。听起来UDP很弱恰恰相反在特定场景下它的简单就是最大的优势。因为少了建立连接、确认、重传、排序等开销UDP的延迟极低吞吐量很高。非常适合实时音视频流如视频会议丢失几帧画面比卡顿等待重传体验更好。DNS查询问个域名地址需要快速响应一次查不到再问一次就好。物联网传感器上报温度传感器每秒上报一次丢一两个数据点无伤大雅。多人在线游戏的状态广播玩家的位置信息需要高频更新旧数据比延迟的数据更有价值。理解了UDP的定位我们写代码时就会明白我们的接收程序核心任务就是在某个端口上持续地、非阻塞地监听所有“喊话”并快速处理。2.2 Python socket库跨平台的网络编程基石Python标准库中的socket模块是对操作系统底层BSD Socket API的封装。无论你在Windows、Linux还是macOS上用socket写出的网络程序其核心逻辑几乎一致。它为我们屏蔽了操作系统的差异提供了统一的接口。选择它而不是第三方库如asyncio、Twisted来入门原因有三零依赖无需安装任何额外包开箱即用。概念纯粹直接接触最核心的socket对象、bind、recvfrom等方法有助于夯实基础。可控性强每一步操作都清晰可见方便调试和理解数据流向。对于UDP我们主要使用socket.SOCK_DGRAM数据报类型。记住这个类型它和TCP的socket.SOCK_STREAM流是两大分支。3. 环境准备与基础代码搭建理论说够了我们开始动手。首先确保你有一个能运行Python的环境我推荐使用VSCode因为它对代码提示和调试都很友好。3.1 创建你的第一个UDP接收端下面是一个最精简、但功能完整的UDP服务器代码。请新建一个udp_server_basic.py文件把以下代码复制进去import socket def basic_udp_server(): # 1. 创建socket对象 # AF_INET 表示使用IPv4地址族 # SOCK_DGRAM 表示使用UDP协议数据报套接字 server_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 绑定IP地址和端口 # 表示绑定到本机所有可用的IPv4地址 # 8888 是自定义的端口号请确保未被其他程序占用 server_address (, 8888) server_socket.bind(server_address) print(fUDP服务器已启动监听在 {server_address[0]}:{server_address[1]}) try: while True: # 3. 接收数据 # recvfrom 方法会阻塞程序直到收到数据 # 1024 是缓冲区大小表示一次最多接收1024字节 # 返回 data字节数据 和 client_address客户端地址和端口 data, client_address server_socket.recvfrom(1024) # 4. 处理数据将字节数据解码为字符串假设发送的是文本 # 这里使用utf-8解码如果发送方是其他编码如gbk需要相应修改 try: message data.decode(utf-8) except UnicodeDecodeError: message repr(data) # 如果解码失败显示字节的表示形式 print(f收到来自 {client_address} 的消息: {message}) # 可选5. 发送回复 # reply f已收到你的消息{message}.encode(utf-8) # server_socket.sendto(reply, client_address) except KeyboardInterrupt: print(\n服务器被用户中断。) finally: # 6. 关闭socket server_socket.close() print(Socket已关闭。) if __name__ __main__: basic_udp_server()逐行解析与注意事项创建Socketsocket.AF_INET指定使用IPv4。如果你的网络环境是IPv6需要使用AF_INET6。SOCK_DGRAM就是UDP的标识。绑定地址(, 8888)是关键。第一个空字符串代表INADDR_ANY即绑定到本机所有网卡IP。你也可以指定为127.0.0.1仅本地环回测试或你的实际内网IP192.168.1.xxx。端口8888可以换成1024-65535之间未被占用的任意端口0-1023是知名端口需要管理员权限。注意如果你遇到错误OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。这明确表示你选择的端口如8888已经被其他程序可能是你之前未关闭的同一个程序或像IIS、Skype等软件占用了。解决方法a) 换一个端口号b) 在命令行用netstat -ano | findstr :8888Windows或lsof -i:8888Linux/macOS找出占用进程并结束它。接收数据recvfrom(1024)是核心。1024是缓冲区大小如果发送的数据包大于这个值多出的部分会被丢弃所以要根据你的应用场景合理设置比如接收视频帧可以设为65507IPv4下UDP单包最大理论值。这个方法会一直等待直到有数据到来。解码数据网络传输的都是字节bytes。发送方用encode()接收方就必须用对应的decode()。这里假设是UTF-8文本这是最通用的编码。如果你接收的是十六进制数据如传感器数据可能需要用binascii.hexlify(data)来转换。发送回复可选UDP虽然无连接但可以双向通信。用sendto()方法指定数据和目标地址就能回复客户端。关闭Socket使用try...finally确保即使程序异常退出socket也能被正确关闭释放系统资源。3.2 首次运行与测试保存udp_server_basic.py。打开终端命令行切换到文件所在目录运行python udp_server_basic.py。如果看到“UDP服务器已启动监听在 0.0.0.0:8888”恭喜服务器已在运行并等待数据。现在我们需要一个客户端来发送数据。最简单的方法是使用我提供的测试工具或者用Python另写一个简单的发送脚本。4. 配套测试工具与API例程详解自己写发送端来测试固然可以但效率太低。我封装了一个带界面的测试工具和一个即拿即用的API模块能极大提升你的开发和调试效率。4.1 UDP测试工具udp_tester.py使用指南这个工具我用Tkinter编写图形界面无需安装额外库。它既能做客户端发送数据也能做服务器接收数据是双向调试的利器。核心功能发送端模式指定目标IP、端口选择发送文本或Hex设置发送间隔进行压力测试。接收端模式绑定本地端口实时显示收到的数据、来源和字节详情。日志面板所有操作和接收到的数据都有清晰记录。如何使用将udp_tester.py文件下载到本地。直接运行python udp_tester.py。测试你的基础服务器确保你的udp_server_basic.py正在运行监听8888端口。在测试工具中切换到“发送端”标签。目标IP填127.0.0.1本地环回端口填8888。在发送内容框输入“Hello UDP!”点击“发送”。立即切回你的服务器终端窗口你应该能看到打印出的消息。工具内部的实用代码片段它的发送核心代码其实很简单你可以借鉴到自己的发送程序中import socket import binascii def send_udp_message(target_ip, target_port, message, is_hexFalse): client_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: if is_hex: # 处理十六进制字符串如 A1 B2 C3 message message.replace( , ) data binascii.unhexlify(message) else: # 处理普通文本 data message.encode(utf-8) client_socket.sendto(data, (target_ip, target_port)) print(f已发送 {len(data)} 字节到 {target_ip}:{target_port}) except Exception as e: print(f发送失败: {e}) finally: client_socket.close()4.2 即用型API例程udp_receiver_api.py对于正式项目我们往往需要更健壮、更易集成的代码。我将核心功能封装成了一个类你可以在任何项目中导入使用。import socket import threading import logging from queue import Queue from typing import Optional, Callable class UDPReceiver: 一个健壮的、支持回调的UDP数据接收器。 def __init__(self, host: str , port: int 8888, buffer_size: int 4096): 初始化UDP接收器。 :param host: 绑定主机默认绑定所有接口 :param port: 绑定端口 :param buffer_size: 接收缓冲区大小 self.host host self.port port self.buffer_size buffer_size self.socket: Optional[socket.socket] None self.receiving False self.receive_thread: Optional[threading.Thread] None # 回调函数当收到数据时被调用callback(data_bytes, client_address) self.callback: Optional[Callable[[bytes, tuple], None]] None # 用于存储接收数据的队列可选 self.data_queue Queue() # 设置日志 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) self.logger logging.getLogger(__name__) def start(self, callback: Optional[Callable[[bytes, tuple], None]] None) - bool: 启动接收线程。 :param callback: 可选的回调函数接收(data, address) :return: 启动是否成功 self.callback callback try: self.socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.bind((self.host, self.port)) # 设置socket超时便于安全退出可选 # self.socket.settimeout(1) self.receiving True self.receive_thread threading.Thread(targetself._receive_loop, daemonTrue) self.receive_thread.start() self.logger.info(fUDP接收器已启动监听于 {self.host}:{self.port}) return True except OSError as e: self.logger.error(f启动失败端口可能被占用: {e}) return False except Exception as e: self.logger.error(f启动时发生未知错误: {e}) return False def _receive_loop(self): 接收数据的内部循环。 while self.receiving and self.socket: try: data, addr self.socket.recvfrom(self.buffer_size) self.logger.debug(f收到来自 {addr} 的 {len(data)} 字节数据) # 放入队列 self.data_queue.put((data, addr)) # 调用回调 if self.callback: try: self.callback(data, addr) except Exception as e: self.logger.error(f回调函数执行出错: {e}) except socket.timeout: # 如果设置了超时这里会捕获用于检查self.receiving状态 continue except OSError as e: # 当socket在recvfrom时被关闭可能会产生错误 if self.receiving: self.logger.error(f接收数据时出错: {e}) break except Exception as e: self.logger.error(f接收循环中发生未知错误: {e}) break def get_latest_data(self, blockTrue, timeoutNone): 从队列中获取最新数据。 :param block: 是否阻塞等待 :param timeout: 阻塞超时时间 :return: (data, address) 或 None try: return self.data_queue.get(blockblock, timeouttimeout) except: return None def stop(self): 停止接收。 self.receiving False if self.socket: self.socket.close() self.socket None if self.receive_thread and self.receive_thread.is_alive(): self.receive_thread.join(timeout2) self.logger.info(UDP接收器已停止。) def __del__(self): 析构函数确保资源被释放。 self.stop() # 使用示例 if __name__ __main__: # 定义处理数据的回调函数 def my_callback(data: bytes, addr: tuple): try: text data.decode(utf-8) print(f[回调]来自 {addr}: {text}) except UnicodeDecodeError: print(f[回调]来自 {addr} 的原始字节: {data.hex()}) # 创建并启动接收器 receiver UDPReceiver(port9999) # 监听9999端口 if receiver.start(callbackmy_callback): print(接收器运行中按 CtrlC 停止...) try: # 主线程可以在这里做其他事情或者简单地等待 while True: # 也可以使用队列方式获取数据而不是依赖回调 # item receiver.get_latest_data(timeout1) # if item: # data, addr item # print(f[队列]来自 {addr}: {data}) import time time.sleep(0.1) except KeyboardInterrupt: print(\n接收到中断信号。) finally: receiver.stop()这个API例程的优势线程化接收主线程不会被recvfrom阻塞可以同时处理其他逻辑。回调机制收到数据后自动调用你定义的函数实现业务逻辑解耦。数据队列提供了另一种消费数据的方式更灵活。完善的日志方便调试和追踪运行状态。资源安全管理使用try...except、finally和析构函数确保Socket被正确关闭。类型提示使用typing模块代码更清晰现代IDE支持更好。你可以把这个UDPReceiver类复制到你的项目工具模块中需要时实例化并启动即可无需再关心socket的底层细节。5. 进阶实战处理复杂场景与提升可靠性基础功能跑通后我们会遇到更实际的问题。下面分享几个进阶场景的解决方案。5.1 场景一如何同时处理多个客户端UDP服务器本身是无连接的一个socket可以接收来自任意客户端的数据。所谓的“处理多个客户端”指的是如何高效地解析不同客户端发来的、格式各异的数据并可能给出不同的响应。解决方案使用“客户端地址”作为标识并设计应用层协议。在recvfrom()方法中我们不仅得到了数据data还得到了客户端的地址client_address一个包含IP和端口的元组如(192.168.1.100, 54321)。这个地址是客户端的源地址我们可以用它来区分不同的发送者。示例一个简单的命令响应服务器假设我们有两个物联网设备一个发温度一个发湿度我们需要分别记录并回复确认。import socket import json from datetime import datetime def multi_client_server(): server_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_socket.bind((, 8888)) print(多客户端处理服务器已启动。) # 用一个字典来记录不同客户端的最新状态 client_status {} try: while True: data, addr server_socket.recvfrom(1024) client_ip, client_port addr try: # 假设客户端发送的是JSON格式数据 payload json.loads(data.decode(utf-8)) device_id payload.get(device_id, unknown) data_type payload.get(type) value payload.get(value) # 更新状态 if client_ip not in client_status: client_status[client_ip] {} client_status[client_ip][data_type] { value: value, last_update: datetime.now().isoformat(), device_id: device_id } print(f[{datetime.now()}] 来自 {client_ip}:{client_port} (设备:{device_id}) - {data_type}: {value}) # 根据数据类型或设备ID发送不同的回复 if data_type temperature: reply_msg json.dumps({status: ACK_TEMP, message: 温度已记录}) elif data_type humidity: reply_msg json.dumps({status: ACK_HUMID, message: 湿度已记录}) else: reply_msg json.dumps({status: ACK_UNKNOWN, message: 数据已接收}) server_socket.sendto(reply_msg.encode(utf-8), addr) except json.JSONDecodeError: print(f来自 {addr} 的数据不是有效的JSON: {data}) server_socket.sendto(bERROR: Invalid JSON, addr) except Exception as e: print(f处理来自 {addr} 的数据时出错: {e}) except KeyboardInterrupt: print(\n服务器关闭。最终客户端状态:) for ip, status in client_status.items(): print(f {ip}: {status}) finally: server_socket.close()关键点addr是区分客户端的唯一标识注意客户端的端口可能变化。使用应用层协议如本例中的JSON格式包含device_id,type,value字段来解析不同含义的数据。根据解析出的内容执行不同的业务逻辑并回复。5.2 场景二如何接收超过缓冲区大小的数据前面提到recvfrom(buffer_size)的缓冲区大小是有限制的。如果发送方发送了一个超过buffer_size的数据包多出的字节会被静默丢弃这不是我们想要的。UDP协议本身不支持分片重组吗网络层IP确实支持分片但重组发生在操作系统内核并且可靠性很差。最佳实践是在应用层自己控制包大小。解决方案发送方分片接收方重组。或者更简单规定一个最大包长。方法A约定最大包长推荐这是最简单可靠的方法。双方约定一个小于等于缓冲区大小的最大数据包长度例如1400字节为IP和UDP头部留出空间。发送方确保不发送超过此长度的包。在接收方我们可以将缓冲区设置得稍大一些比如1500以太网MTU的常见值并检查接收到的数据长度。MAX_PACKET_SIZE 1400 data, addr sock.recvfrom(MAX_PACKET_SIZE 100) # 留点余量 if len(data) MAX_PACKET_SIZE: print(f警告来自 {addr} 的数据包过大({len(data)}字节)可能已丢失部分数据。)方法B应用层分片重组复杂如果需要传输大文件需要在应用层实现发送方将数据分割成固定大小的片段如1024字节为每个片段添加序号、总片数等头部信息然后逐个发送。接收方接收所有片段根据序号排序、重组直到收齐所有片段。 这本质上是在UDP之上实现了一个简单的可靠传输协议复杂度高除非有强实时性要求且不能使用TCP否则不建议自己造轮子。可以考虑使用像QUICHTTP/3的基础这样的成熟协议库。5.3 场景三如何实现非阻塞接收不让程序“卡住”默认的socket.recvfrom()是阻塞的程序会停在那里等待数据。如果我们想在等待数据的同时还能执行其他任务比如更新UI、处理定时事件就需要非阻塞模式。解决方案设置socket超时或使用select模块。方法A设置超时socket.settimeout这是最简单的方法。设置一个超时时间秒如果在此期间没有数据到达recvfrom会抛出一个socket.timeout异常。import socket def nonblocking_server_with_timeout(): sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((, 8888)) sock.settimeout(2.0) # 设置2秒超时 print(非阻塞服务器超时模式已启动。) while True: try: data, addr sock.recvfrom(1024) print(f收到数据: {data.decode()} from {addr}) except socket.timeout: # 超时可以在这里执行其他任务 print(等待超时执行其他检查任务...) # 例如检查资源、发送心跳包等 # time.sleep(0.1) # 避免CPU空转 continue except KeyboardInterrupt: break sock.close()方法B使用select更高效的多路复用select模块允许你监控多个socket或文件描述符哪个有数据了就去读哪个非常高效。它特别适合需要同时处理多个连接或混合TCP/UDP的场景。import socket import select def nonblocking_server_with_select(): sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((, 8888)) # 将socket设置为非阻塞模式另一种方式 # sock.setblocking(0) print(非阻塞服务器select模式已启动。) read_list [sock] # 需要监控读事件的socket列表 while True: # select.select(rlist, wlist, xlist[, timeout]) # 监控rlist中的socket直到它们可读 readable, _, _ select.select(read_list, [], [], 1.0) # 1秒超时 for s in readable: if s is sock: data, addr sock.recvfrom(1024) print(f收到数据: {data.decode()} from {addr}) # 无论是否有数据这里都会每秒执行一次可以放置其他循环任务 # print(执行其他循环任务...)选择建议如果逻辑简单只是不想永久阻塞用settimeout最简单。如果需要同时监控多个socket例如一个UDP socket 一个标准输入或者要构建高性能的事件循环select或其更高效的变体poll/epollLinux/kqueueBSD是标准选择。对于现代Python高性能网络编程直接使用asyncio库是更优的选择它内部就使用了这些机制。6. 常见问题排查与调试技巧实录即使代码看起来完美在实际运行中还是会遇到各种问题。下面是我踩过坑后总结的排查清单。6.1 错误“[WinError 10048] 通常每个套接字地址只允许使用一次”这是Windows上最常见的错误Linux/macOS上类似错误是“Address already in use”。原因与解决方案原因排查命令Windows解决方案程序未正常关闭端口处于TIME_WAIT状态netstat -ano | findstr :你的端口号等待1-2分钟系统回收端口或更换端口。其他软件占用了端口同上查看PID在任务管理器中查找对应进程。结束冲突进程或更换端口。常见占用端口的软件Skype、IIS、SQL Server等。本程序多个实例同时运行检查是否打开了多个命令行窗口运行了同一个脚本。关闭多余的实例。绑定到特定IP但该IP不可用检查绑定的IP地址是否正确如192.168.1.xxx是否为本机IP。绑定到所有接口或正确的IP。根治方法代码层面在创建socket后、绑定前设置SO_REUSEADDR选项。这允许socket绑定到一个处于TIME_WAIT状态的地址。sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 关键行 sock.bind((, 8888))注意在Windows上SO_REUSEADDR的行为可能与Unix系统略有不同但通常对UDP有效。对于TCPWindows上更常用的是SO_EXCLUSIVEADDRUSE但UDP问题不大。6.2 收不到数据一步步定位如果服务器启动了客户端也发送了但服务器没反应按以下顺序排查防火墙这是头号嫌疑犯。确保你的操作系统防火墙和杀毒软件允许Python或python.exe进行网络通信并开放了对应的端口如8888/UDP。Windows在“Windows Defender 防火墙”中添加入站规则。Linux/macOS使用sudo ufw allow 8888/udp如果使用UFW或配置iptables。IP地址是否正确本地测试客户端发送到127.0.0.1或localhost。局域网测试服务器绑定或本机局域网IP如192.168.1.100客户端发送到服务器的局域网IP而不是127.0.0.1。检查IP在服务器上运行ipconfigWindows或ifconfig/ip addrLinux/macOS查看正确IP。端口是否正确确认客户端发送的目标端口与服务器绑定的端口完全一致。使用网络调试工具在服务器端抓包用tcpdumpLinux/macOS或Wireshark全平台监听对应的网卡和端口udp port 8888看数据包是否真的到达了网卡。如果抓不到包问题出在客户端或网络路径上。如果抓到了包但程序没收到问题出在你的Python程序。使用我的测试工具用我的udp_tester.py同时开启接收模式绑定8888和发送模式发到127.0.0.1:8888看工具自己能否收到可以快速判断是代码问题还是环境问题。代码逻辑问题检查recvfrom是否在正确的循环内。检查打印或日志语句是否被执行。尝试在recvfrom前后添加简单打印确认程序执行流。6.3 数据乱码或解码错误表现收到数据后用decode(utf-8)解码时抛出UnicodeDecodeError。原因与解决编码不匹配发送方用gbk编码接收方用utf-8解码。解决方案与发送方约定统一的编码或尝试常见编码。# 尝试多种解码方式 encodings [utf-8, gbk, gb2312, latin-1] for enc in encodings: try: message data.decode(enc) print(f成功以 {enc} 解码: {message}) break except UnicodeDecodeError: continue else: print(无法解码显示十六进制:, data.hex())发送的不是文本发送的是图片、音频、自定义二进制协议等。解决方案直接处理字节数据不要解码。# 如果是数值可能需要解包 import struct # 假设发送的是一个小端序的32位整数和一个单精度浮点数 int_val, float_val struct.unpack(If, data) # 小端序I无符号intf浮点 print(f收到数值: int{int_val}, float{float_val})数据被截断缓冲区太小只收到了数据包的一部分导致解码失败。确保recvfrom的缓冲区大小足够。6.4 性能瓶颈与优化建议当需要处理海量UDP包时例如日志收集简单的循环可能成为瓶颈。使用更大的缓冲区适当增大recvfrom的缓冲区大小减少系统调用次数。但不要超过65507IPv4 UDP最大载荷。考虑使用socket.SO_RCVBUF调整系统级的接收缓冲区大小防止在应用层来不及处理时内核缓冲区溢出导致丢包。sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024 * 1024) # 设置为1MB # 注意实际大小可能会被系统调整为接近的2的幂次方值可以用getsockopt读取实际值 actual_size sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)切换到高性能框架如果性能要求极高考虑使用asyncioloop.create_datagram_endpoint: 利用异步IO单线程处理高并发。multiprocessing或concurrent.futures.ThreadPoolExecutor: 利用多核CPU将接收逻辑放到独立进程/线程池中处理避免处理逻辑阻塞接收循环。业务逻辑异步化在recvfrom循环中只做最简单的数据接收和放入队列如Queue然后由后台的工作线程或进程从队列中取出数据进行耗时处理如写入数据库、复杂计算。最后关于选择TCP还是UDP我的个人经验是除非你非常确定你的应用能接受丢包、乱序并且低延迟比可靠性更重要否则优先选择TCP。UDP编程需要你在应用层考虑更多可靠性问题。但一旦你掌握了UDP你对网络编程的理解会上一个台阶。希望这个从原理到工具、从基础到进阶的指南能帮你顺利跨过UDP编程的门槛。