用Python实现UDP简易聊天程序!先搞懂:为啥UDP不能直接做聊天软件?

用Python实现UDP简易聊天程序!先搞懂:为啥UDP不能直接做聊天软件?

前言

平时我们熟知的微信、QQ这类聊天软件,底层网络方案各有不同。很多初学者第一想法:直接用UDP写聊天工具,代码简单、不用建立连接、速度快。

但是很多人写完Demo后就踩坑:消息偶尔收不到、重复收到消息、消息顺序错乱。
原因很简单:UDP是无连接、不可靠传输协议。内核不保证报文送达、不保证有序、丢包不会通知发送方。

本文先实现一套原生UDP点对点聊天Demo(服务端+客户端,可以互相收发文本消息),模拟简易聊天窗口。再结合代码讲解原生UDP聊天存在的致命缺陷,衔接上一篇《如何让UDP实现可靠传输》的知识点,带大家理解:商用聊天软件不会直接裸UDP通信。

环境:Python3.x,无需额外安装第三方库,使用内置socket模块。
演示效果:两个终端,互相发送文字,实现双向聊天。

一、基础知识点:UDP通信特点回顾

  1. 无连接:通信前不需要握手,发送数据直接指定IP和端口;
  2. 尽最大努力交付:没有确认、没有重传,丢包直接消失;
  3. 支持双向通信:同一个Socket既可以收消息,也可以发消息;
  4. 面向报文:不会拆分合并数据包;
  5. 优点:开销小、延迟低;缺点:不可靠。

⚠️ 重要:TCP分为服务端/客户端;UDP模式更灵活,下面示例我们采用对等模式(P2P),两端代码几乎一致,只要填写对方IP端口,互相收发消息。

二、Python UDP双向聊天完整代码

文件名:udp_chat.py

importsocketimportthreadingclassUDPChat:def__init__(self,local_port:int,remote_ip:str,remote_port:int):# 创建UDP socketself.sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)# 绑定本机端口,固定端口方便对方连接self.sock.bind(("0.0.0.0",local_port))self.remote_ip=remote_ip self.remote_port=remote_portprint(f"✅ UDP聊天启动成功!本机端口:{local_port}")print(f"🎯 目标地址:{self.remote_ip}:{self.remote_port}")print("💡 输入消息发送,exit退出程序\n")defreceive_msg(self):"""持续接收消息的循环,单独线程运行"""whileTrue:try:data,addr=self.sock.recvfrom(1024)msg=data.decode("utf-8")print(f"\n【对方】{msg}")print("你:",end="")exceptExceptionase:print("接收消息异常:",e)breakdefsend_msg(self):"""读取控制台输入,发送消息"""whileTrue:content=input("你:")ifcontent.lower()=="exit":print("🔚 退出聊天程序")self.sock.close()break# 发送文本消息self.sock.sendto(content.encode("utf-8"),(self.remote_ip,self.remote_port))defstart(self):# 开启子线程负责接收消息(阻塞recvfrom不能阻塞输入)recv_thread=threading.Thread(target=self.receive_msg,daemon=True)recv_thread.start()# 主线程负责发送消息self.send_msg()if__name__=="__main__":# ========== 配置区域,两端修改此处参数 ==========# A机器配置(机器A)# 本机端口 8888,对方IP为机器BIP,对方端口9999chat=UDPChat(local_port=8888,remote_ip="127.0.0.1",remote_port=9999)# B机器配置(机器B,启动时注释上面,启用下面这行)# chat = UDPChat(local_port=9999, remote_ip="127.0.0.1", remote_port=8888)chat.start()

三、运行测试步骤

本地测试(同一台电脑开启两个终端)

  1. 终端1(A端),使用配置:
chat=UDPChat(local_port=8888,remote_ip="127.0.0.1",remote_port=9999)
  1. 终端2(B端)切换配置:
chat=UDPChat(local_port=9999,remote_ip="127.0.0.1",remote_port=8888)
  1. 分别运行python udp_chat.py,互相输入文字即可聊天。

局域网/公网注意事项

  1. 局域网通信填写对方内网IP;
  2. 公网通信需要端口映射、关闭防火墙;
  3. 云服务器务必在安全组开放对应UDP端口。

运行效果图文字示例:

✅ UDP聊天启动成功!本机端口:8888 🎯 目标地址:127.0.0.1:9999 💡 输入消息发送,exit退出程序 你:哈喽,测试UDP聊天 【对方】收到啦! 你:

四、Demo暴露出原生UDP做聊天软件的4个致命问题

1. 消息丢失(最严重)

当网络波动、路由器拥塞时,sendto执行成功仅仅代表数据交给系统内核,无法确认对方收到消息
就像微信发消息对方收不到,且你完全不知情。

TCP会等待ACK,超时自动重传;原生UDP没有任何机制。

2. 消息乱序

发送顺序:消息1 → 消息2 → 消息3
网络转发原因,接收顺序可能变成:消息3 → 消息1 → 消息2
聊天场景会出现对话语序错乱,严重影响体验。

3. 消息重复

网络延迟引发路由重复投递,同一条消息多次弹出。

4. 无法感知对方是否离线

UDP无连接,你持续发送消息,对方下线后本地不会得到任何通知。不知道对方在线状态。

五、思考:真正的聊天软件如何解决?

方案分三类:

  1. 基于TCP:微信早期部分业务、传统IM,依靠TCP天然可靠;缺点握手延迟,网络切换容易断连。
  2. 可靠UDP(应用层封装):在UDP之上增加序列号、ACK确认、超时重传(KCP、QUIC)。很多游戏、实时语音、部分新式IM采用该方案。
  3. 混合方案:信令消息TCP可靠传输,媒体语音视频使用优化后的UDP。

我们上一篇博客讲解的【可靠UDP实现方案】,正好可以改造这个聊天Demo:
增加序列号、ACK应答、超时重传,就能解决消息丢失、乱序、重复问题。

六、拓展优化思路(你可以自行改造代码)

  1. 给每条消息增加唯一序列号;
  2. 接收端回复ACK确认收到消息;
  3. 发送端未收到ACK则启动重传;
  4. 增加心跳包,检测对方在线状态;
  5. 增加乱序缓冲区,保证消息有序展示;
  6. 增加用户名、消息时间戳;
  7. 增加简单异常断线处理。

七、总结

这份代码实现了极简UDP点对点文本聊天,几分钟就能跑通,直观感受到UDP双向通信。
但是一定要分清:Demo能跑 ≠ 可以生产使用。裸UDP天生缺乏可靠性机制,直接拿来做聊天工具会出现消息丢失等各类问题。

如果追求低延迟同时需要消息可靠,不要直接裸写UDP,两种选择:

  • 方案一:直接使用QUIC / KCP成熟可靠UDP库;
  • 方案二:参考可靠UDP原理,在应用层自主实现确认、重传、有序机制。

配套延伸阅读:《如何让UDP实现可靠传输:原理、问题与自研方案实践》