如何用Python构建客户端无需端口转发的服务器客户端应用?
核心实现原理
你观察到的这类场景本质是利用了NAT网络的出站连接规则,没有用到特殊的黑科技
首先明确一个前提:绝大多数民用、办公网络环境下,路由器默认禁止外部主动向内发起的未关联连接,但完全允许内部设备主动向外发起的连接,并且会自动为这个连接维持临时的端口映射条目,只要连接不中断,外部服务器就可以通过这个已经建立好的连接双向收发数据,完全不需要用户手动配置端口转发。
Discord、Chrome这类应用的通信逻辑就是:
- 客户端启动后主动向公网的自有服务器发起TCP/WebSocket连接(通常用80、443这类常用出站端口,基本不会被运营商或者路由器拦截)
- 连接建立后保持长连接不中断(通过定期发心跳包维持存活)
- 服务器需要给客户端发数据的时候,直接通过已经建立好的这个连接回传即可,不需要主动向客户端发起新连接
检索关键词补充
你之前搜不到对应内容可以用下面几个关键词检索:
- 客户端长连接推送
- 出站连接双向通信
- WebSocket全双工通信
- 服务端推送技术
Python实现方案
最简单的实现可以用WebSocket协议,已经封装了全双工双向通信的能力,不需要自己处理底层连接维持逻辑:
依赖安装
pip install websockets
服务端示例代码(需要部署在有公网IP的服务器上)
import asyncio import websockets # 存储所有已连接的客户端 connected_clients = set() async def handle_client(websocket): connected_clients.add(websocket) try: # 循环接收客户端发送的消息 async for message in websocket: print(f"收到客户端消息: {message}") # 示例:给所有其他客户端广播消息 for client in connected_clients: if client != websocket: await client.send(f"服务器转发: {message}") finally: connected_clients.remove(websocket) async def main(): async with websockets.serve(handle_client, "0.0.0.0", 8765): await asyncio.Future() # 保持服务永久运行 if __name__ == "__main__": asyncio.run(main())
客户端示例代码(运行在用户内网设备上,不需要任何端口转发)
import asyncio import websockets async def client_run(): # 此处替换为你公网服务器的IP和端口 async with websockets.connect("ws://你的公网服务器IP:8765") as websocket: # 客户端主动发消息给服务器 await websocket.send("客户端上线") # 循环接收服务器主动推送的消息 async for message in websocket: print(f"收到服务器推送: {message}") if __name__ == "__main__": asyncio.run(client_run())
注意事项
- 服务端必须部署在有公网IP的服务器上,防火墙需要开放对应端口(示例用的8765)
- 如果要兼容更严格的网络环境,可以把WebSocket换成基于443端口的WSS(加密WebSocket),几乎不会被网络拦截
- 长时间运行的场景需要加心跳包逻辑,避免路由器的NAT映射超时自动断开连接
内容的提问来源于stack exchange,提问作者Linxs
相关产品推荐
相关产品推荐

