当前位置: 首页 > news >正文

Tornado WebSocket实时聊天实例

在 Python 3 Tornado 中使用 WebSocket 非常直接。你需要创建一个继承自 tornado.websocket.WebSocketHandler 的类,并实现它的几个关键方法。

下面是一个简单的示例,演示了如何创建一个 WebSocket 服务器,该服务器会接收客户端发送的消息,并在其前面加上 "Echo: " 前缀后回显给客户端。同时,它还会将收到的消息广播给所有连接的客户端。

1. 服务器端 (Python - server.py)

import tornado.ioloop
import tornado.web
import tornado.websocket
import logging
import uuid # 用于给客户端一个唯一ID (可选)logging.basicConfig(level=logging.INFO)class ChatSocketHandler(tornado.websocket.WebSocketHandler):# 使用一个类级别的集合来存储所有活动的 WebSocket 连接clients = set()client_details = {} # 可选:存储客户端更多信息def open(self):"""当一个新的 WebSocket 连接建立时调用"""self.client_id = str(uuid.uuid4()) # 给每个连接一个唯一IDChatSocketHandler.clients.add(self)ChatSocketHandler.client_details[self] = {"id": self.client_id}logging.info(f"New client connected: {self.client_id} from {self.request.remote_ip}")self.write_message(f"Welcome! Your ID is {self.client_id}")self.broadcast(f"Client {self.client_id} has joined.", exclude_self=True)def on_message(self, message):"""当从客户端接收到消息时调用"""logging.info(f"Received message from {self.client_id}: {message}")# 简单的回显# self.write_message(f"You said: {message}")# 广播消息给所有客户端self.broadcast(f"{self.client_id} says: {message}")def on_close(self):"""当 WebSocket 连接关闭时调用"""ChatSocketHandler.clients.remove(self)if self in ChatSocketHandler.client_details:del ChatSocketHandler.client_details[self]logging.info(f"Client {self.client_id} disconnected.")self.broadcast(f"Client {self.client_id} has left.", exclude_self=True)def check_origin(self, origin):"""允许跨域 WebSocket 连接。在生产环境中,你应该更严格地检查 origin。例如:allowed_origins = ["http://localhost:8000", "https://yourdomain.com"]return origin in allowed_origins"""logging.info(f"Checking origin: {origin}")return True # 暂时允许所有来源@classmethoddef broadcast(cls, message, exclude_self=False, sender=None):"""辅助方法,向所有连接的客户端广播消息"""logging.info(f"Broadcasting message: {message}")for client in cls.clients:if exclude_self and sender and client == sender:continuetry:client.write_message(message)except tornado.websocket.WebSocketClosedError:logging.warning(f"Failed to send to a closed socket for client {cls.client_details.get(client, {}).get('id', 'unknown')}")except Exception as e:logging.error(f"Error sending message to client {cls.client_details.get(client, {}).get('id', 'unknown')}: {e}")def make_app():return tornado.web.Application([(r"/ws", ChatSocketHandler), # 将 /ws 路径映射到处理器])if __name__ == "__main__":app = make_app()port = 8888app.listen(port)logging.info(f"WebSocket server started on port {port}")tornado.ioloop.IOLoop.current().start()

2. 客户端 (HTML + JavaScript - client.html)

<!DOCTYPE html>
<html>
<head><title>Tornado WebSocket Chat</title><style>#chatbox {width: 400px;height: 300px;border: 1px solid #ccc;overflow-y: scroll;padding: 10px;margin-bottom: 10px;}.message {margin-bottom: 5px;}</style>
</head>
<body><h1>Tornado WebSocket Chat</h1><div id="chatbox"></div><input type="text" id="messageInput" placeholder="Type your message here..." size="50"><button onclick="sendMessage()">Send</button><script>const chatbox = document.getElementById('chatbox');const messageInput = document.getElementById('messageInput');// 确保 WebSocket URL 与服务器端配置一致const socket = new WebSocket("ws://localhost:8888/ws"); socket.onopen = function(event) {addMessageToChatbox("System: Connected to WebSocket server.");console.log("WebSocket connection opened:", event);};socket.onmessage = function(event) {console.log("Message from server:", event.data);addMessageToChatbox("Server: " + event.data);};socket.onclose = function(event) {if (event.wasClean) {addMessageToChatbox(`System: Connection closed cleanly, code=${event.code} reason=${event.reason}`);} else {addMessageToChatbox('System: Connection died');}console.log("WebSocket connection closed:", event);};socket.onerror = function(error) {addMessageToChatbox("System: WebSocket Error: " + error.message);console.error("WebSocket Error:", error);};function sendMessage() {const message = messageInput.value;if (message.trim() !== "") {socket.send(message);// addMessageToChatbox("You: " + message); // 也可以等服务器广播回来messageInput.value = "";}}messageInput.addEventListener("keypress", function(event) {if (event.key === "Enter") {sendMessage();}});function addMessageToChatbox(message) {const messageElement = document.createElement('div');messageElement.classList.add('message');messageElement.textContent = message;chatbox.appendChild(messageElement);chatbox.scrollTop = chatbox.scrollHeight; // 自动滚动到底部}</script>
</body>
</html>

如何运行:

  1. 保存文件: 将 Python 代码保存为 server.py,将 HTML 代码保存为 client.html
  2. 安装 Tornado: 如果你还没有安装,请执行:
    pip install tornado
    
  3. 运行服务器: 打开终端或命令提示符,导航到保存 server.py 的目录,然后运行:
    python server.py
    
    你应该会看到类似 “WebSocket server started on port 8888” 的输出。
  4. 打开客户端: 在你的 Web 浏览器中打开 client.html 文件 (可以直接双击文件,或者通过 file:///path/to/client.html 访问)。你可以打开多个浏览器窗口或标签页来模拟多个客户端。

关键点解释:

  • tornado.websocket.WebSocketHandler: 这是处理 WebSocket 连接的核心类。
  • open(): 当客户端成功建立 WebSocket 连接后,此方法被调用。你可以在这里进行初始化操作,比如将客户端实例添加到一个列表中以便后续广播。
  • on_message(message): 当服务器从客户端接收到一条消息时,此方法被调用。message 参数是客户端发送的数据(通常是字符串,也可以配置为接收二进制数据)。
  • on_close(): 当连接关闭时(无论是客户端主动关闭还是服务器关闭,或者由于网络错误),此方法被调用。在这里进行清理工作,比如从活动客户端列表中移除该连接。
  • write_message(message): 此方法用于向连接的客户端发送消息。
  • check_origin(self, origin): 这个方法用于安全目的,决定是否接受来自特定源(origin)的 WebSocket 连接。默认情况下,Tornado 会拒绝跨域的 WebSocket 连接。在开发环境中,返回 True 可以方便测试。在生产环境中,你应该仔细配置允许的源列表以防止 CSRF 类型的攻击。
  • clients = set(): 这是一个类级别的集合,用于跟踪所有当前连接的客户端 WebSocketHandler 实例。这使得向所有客户端广播消息成为可能。
  • broadcast() (自定义方法): 这是一个我们添加的类方法,用于方便地向 clients 集合中的所有客户端发送消息。
  • 客户端 WebSocket API:
    • new WebSocket("ws://localhost:8888/ws"): 创建一个新的 WebSocket 连接。ws:// 表示普通的 WebSocket,如果是加密的,则使用 wss://
    • socket.onopen: 连接成功建立时的回调。
    • socket.onmessage: 收到服务器消息时的回调。event.data 包含消息内容。
    • socket.onclose: 连接关闭时的回调。
    • socket.onerror:发生错误时的回调。
    • socket.send(message): 向服务器发送消息。

进一步的考虑:

  • 错误处理: 更健壮的错误处理,例如 write_message 可能会因为客户端突然断开而失败。
  • 消息格式: 对于复杂应用,通常使用 JSON 作为消息格式,方便传输结构化数据。你需要在服务器端 json.loads() 接收到的消息,并在发送前 json.dumps()
  • 认证与授权: 对于需要用户登录的应用,你需要在 WebSocket 连接建立时(可能通过 open() 或在 HTTP 升级握手阶段)进行用户认证。
  • 状态管理: 对于更复杂的应用,你可能需要在服务器端为每个客户端维护更复杂的状态。
  • 扩展性: 对于大量并发连接,单个 Tornado 进程可能不够。你可能需要考虑使用多个进程(例如通过 supervisord 运行多个 Tornado 实例)并使用像 Redis Pub/Sub 这样的消息队列来在进程间广播 WebSocket 消息。
  • SSL/TLS (wss://): 在生产环境中,务必使用 wss:// (WebSocket Secure) 来加密通信。这需要在 Tornado 应用启动时配置 SSL 选项。

相关文章:

  • 汽车高速通信的EMC挑战
  • Hive的数据倾斜是什么?
  • Unity3D ET框架游戏脚本系统解析
  • 世冠科技亮相中汽中心科技周MBDE会议,共探汽车研发数字化转型新路径
  • 云原生 Cloud Native Build (CNB)使用初体验
  • 什么是分片(Shard)?为什么要使用分片?
  • Unity链接Mysql 数据库实现注册登录
  • php:5.6-apache Docker镜像中安装 gd mysqli 库 【亲测可用】
  • 服务器Docker容器创建与VScode远程连接SSH使用
  • 单卡4090部署Qwen3-32B-AWQ(4bit量化)-vllm
  • 利用 Synonyms 中文近义词库调优 RAG 服务,基于 Ollama, DeepSeek R1, Langchain
  • 响应式系统与Spring Boot响应式应用开发
  • 鸿蒙OSUniApp复杂表单与动态验证实践:打造高效的移动端表单解决方案#三方框架 #Uniapp
  • 生动形象理解CNN
  • HCIP:MPLS静态LSP的配置及抓包
  • 2.从0开始搭建vue项目(node.js,vue3,Ts,ES6)
  • ASP.NET MVC添加模型示例
  • 全志科技携飞凌嵌入式T527核心板亮相OpenHarmony开发者大会
  • springboot项目下面的单元测试注入的RedisConnectionFactory类redisConnectionFactory值为什么为空呢?
  • 鸿蒙OSUniApp导航栏组件开发:打造清新简约的用户界面#三方框架 #Uniapp
  • 绍兴优秀做网站的/搜索网站关键词
  • 有没有IT做兼职的网站/淘数据官网
  • 网站推广建设加盟/网络营销与管理
  • 网站开发用什么开发/镇江seo快速排名
  • 免费图片编辑网站/2022真实新闻作文400字
  • 做网站选什么配置电脑/搜狗seo快速排名公司