Tornado WebSocket实时聊天实例

news2025/6/2 16:45:48

在 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()) # 给每个连接一个唯一ID
        ChatSocketHandler.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 # 暂时允许所有来源

    @classmethod
    def 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:
                continue
            try:
                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 = 8888
    app.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 选项。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2394135.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

开源即战力!从科研到商用:Hello Robot 移动操作机器人Stretch 3多模态传感融合(RGB-D/激光/力矩)控制方案

科研领域对机器人技术的需求日益增长&#xff0c;Hello Robot的移动操作机器人Stretch 3凭借其灵活性和性能满足了这一需求。其模块化设计、开源架构和高精度传感控制能力&#xff0c;使科研人员能够顺利开展实验。Stretch 3以其独特的移动操作能力&#xff0c;为科研探索提供了…

元胞自动机(Cellular Automata, CA)

一、什么是元胞自动机&#xff08;Cellular Automata, CA&#xff09; 元胞自动机&#xff08;CA&#xff09; 是一种基于离散时间、离散空间与规则驱动演化的动力系统&#xff0c;由 冯诺依曼&#xff08;John von Neumann&#xff09; 于1940年代首次提出&#xff0c;用于模…

智能手表单元测试报告(Unit Test Report)

📄 智能手表单元测试报告(Unit Test Report) 项目名称:Aurora Watch S1 模块版本:Firmware v1.0.4 测试阶段:模块开发完成后的单元测试 报告编号:AW-S1-UTR-2025-001 测试负责人:赵磊(软件架构师) 报告日期:2025-xx-xx 一、测试目的 通过对智能手表关键功能模块进…

微深节能 码头装卸船机定位与控制系统 格雷母线

微深节能码头装卸船机定位与控制系统&#xff1a;格雷母线技术赋能港口作业智能化升级 在现代化港口散货装卸作业中&#xff0c;装卸船机是连接船舶与陆域运输的核心枢纽设备。传统装卸船机依赖人工操作&#xff0c;存在定位偏差大、动态协同难、安全风险高等痛点。微深节能基于…

Python实现P-PSO优化算法优化循环神经网络LSTM分类模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 随着深度学习技术的迅猛发展&#xff0c;循环神经网络&#xff08;RNN&#xff09;及其变体LSTM&#xff08;Long S…

Scratch节日 | 龙舟比赛 | 端午节

端午节快乐&#xff01; 这款专为孩子们打造的Scratch游戏——《龙舟比赛》&#xff0c;让你在掌控龙舟的竞速中&#xff0c;沉浸式体验中华传统节日的魅力&#xff01; &#x1f3ae; 游戏亮点 节日氛围浓厚&#xff1a;化身龙舟选手&#xff0c;在波涛汹涌的河流中展开刺激竞…

electron开发百度桌面应用demo及如何打包应用

1.开发入口文件main.js 1-1 加载百度URL const { app, BrowserWindow, nativeImage } require(electron) const path require(node:path)const createWindow () > {const win new BrowserWindow({width: 800,height: 600,})//加载百度URLwin.loadURL(https://www.baid…

关于用Cloudflare的Zero Trust实现绕过备案访问国内站点说明

cloudflare 是一个可免费的CDN&#xff0c;CDN&#xff08;Content Delivery Network&#xff0c;内容分发网络&#xff09;加速国内网站&#xff0c;通常是已备案的。Zero Trust类似FRP&#xff0c;可以将请求转发到目标服务器。在使用Zero Trust绕过备案访问国内网站需要&…

方正字库助力华为,赋能鸿蒙电脑打造全场景字体解决方案

2025年5月19日&#xff0c;搭载华为鸿蒙操作系统的鸿蒙电脑&#xff0c;面向用户推出集AI智能、互联流畅、安全保障和精致体验于一体的全新办公系统。作为鸿蒙生态核心字体服务商&#xff0c;方正字库为此次提供了全面的系统字体支持&#xff0c;涵盖中文、西文及符号三大类字库…

STM32 串口通信①:USART 全面理解 + 代码详解

一 前言 本篇文章并不会系统的从零开始讲起&#xff0c;适合大家对USART有一定的学习&#xff0c;再看本篇文章会有一定的收获&#xff0c;祝大家在本文中&#xff0c;吸收到新的知识。 二 通信方式 1&#xff09;按数据传输的方式分&#xff08;这就是“串行 vs 并行”&…

【Java Web】速通CSS

参考笔记:JavaWeb 速通CSS_java css-CSDN博客 目录 一、CSS入门 1. 基本介绍 2. 作用 二、CSS的3种引入方式 1. 行内式 1.1 示例代码 1.2 存在问题 2. 写在head标签的style子标签中 2.1 示例代码 2.2 存在问题 3.以外部文件的形式引入(开发中推荐使用)⭐⭐⭐ 3.1 说明 3…

NHANES指标推荐:ALI

文章题目&#xff1a;A cross-sectional study examining the relationship between the advanced lung cancer inflammation index and prostate cancer 中文标题&#xff1a;一项检查晚期肺癌炎症指数与前列腺癌之间关系的横断面研究 发表杂志&#xff1a;Journal of Health…

UE5 Mat HLSL - Load

特性Load()Sample()输入类型整数索引&#xff08;int2/int3&#xff09;浮点 UV 采样器状态&#xff08;SamplerState&#xff09;数据获取精确读取指定位置的原始数据基于 UV 插值和过滤后的数据典型用途精确计算、非过滤访问&#xff08;如物理模拟&#xff09;纹理贴图渲染…

【Unity笔记】Unity WASD+QE 控制角色移动与转向(含 Shift 加速)实现教程

摘要&#xff1a; 在 Unity 游戏开发中&#xff0c;键盘控制角色的移动与转向是基础功能之一。本文详细讲解如何使用 C# 实现基于 WASD 移动、QE 转向 与 Shift 加速奔跑 的角色控制器&#xff0c;适用于第一人称、第三人称、自由漫游等场景。通过直观的 Transform 控制方法与可…

HTML5 列表、表格与媒体元素、页面结构分析

1. 列表 无序列表 有序列表 定义列表 列表对比 2. 表格 跨列 跨行 跨行和跨列 3. HTML5的媒体元素 视频元素 注意&#xff1a;autoplay现在很多浏览器不支持了&#xff01; 音频元素 4. 页面结构分析 5. 总结

换宽带ip地址会变吗?同一个宽带如何切换ip地址

在当今互联网时代&#xff0c;IP地址作为网络设备的"身份证"&#xff0c;其重要性不言而喻。许多用户在使用宽带时都会遇到这样的疑问&#xff1a;换宽带IP地址会变吗&#xff1f;同一个宽带如何切换IP地址&#xff1f;本文将深入探讨这一问题&#xff0c;帮助读者全…

第100+41步 ChatGPT学习:R语言实现误判病例分析

本期是《第33步 机器学习分类实战&#xff1a;误判病例分析》的R版本。 尝试使用Deepseek-R1来试试写代码&#xff0c;效果还不错。 下面上R语言代码&#xff0c;以Xgboost为例&#xff1a; # 加载必要的库 library(caret) library(pROC) library(ggplot2) library(xgboost)…

贝锐蒲公英工业路由器R300A海外版:支持多国4G频段,全球组网

为更好地满足全球部署和企业出海项目的多样化需求&#xff0c;贝锐蒲公英异地组网工业路由器R300A海外版全新上市&#xff0c;并已正式上架速卖通&#xff01;无论是跨国分支机构协同办公&#xff0c;还是海外工厂设备远程运维&#xff0c;R300A海外版都能为企业提供灵活、高性…

[特殊字符] 超强 Web React版 PDF 阅读器!支持分页、缩放、旋转、全屏、懒加载、缩略图!

在现代 Web 项目中&#xff0c;PDF 浏览是一个常见需求&#xff1a;从政务公文到合同协议&#xff0c;PDF 文件无处不在。但很多方案要么体验不佳&#xff0c;要么集成复杂。今天&#xff0c;我给大家带来一个开箱即用、功能全面的 PDF 预览组件 —— [PDFView](https://www.np…

wireshark分析国标rtp ps流

1.将抓到的tcp或者udp视频流使用decode as 转为rtp包 2.电话->RTP->RTP播放器 选择Export 里面的Payload 就可以导出原始PS流