Tornado 之 WebSocket

7.3 WebSocket

WebSocket是HTML5规范中新提出的客户端-服务器通讯协议,协议本身使用新的ws://URL格式。

WebSocket 是独立的、创建在 TCP 上的协议,和 HTTP 的唯一关联是使用 HTTP 协议的101状态码进行协议切换,使用的 TCP 端口是80,可以用于绕过大多数防火墙的限制。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端直接向客户端推送数据而不需要客户端进行请求,两者之间可以创建持久性的连接,并允许数据进行双向传送。

目前常见的浏览器如 Chrome、IE、Firefox、Safari、Opera 等都支持 WebSocket,同时需要服务端程序支持 WebSocket。

1. Tornado的WebSocket模块

Tornado提供支持WebSocket的模块是tornado.websocket,其中提供了一个WebSocketHandler类用来处理通讯。

WebSocketHandler.open()

当一个WebSocket连接建立后被调用。

WebSocketHandler.on_message(message)

当客户端发送消息message过来时被调用,注意此方法必须被重写。

WebSocketHandler.on_close()

当WebSocket连接关闭后被调用。

WebSocketHandler.write_message(message, binary=False)

向客户端发送消息messagea,message可以是字符串或字典(字典会被转为json字符串)。若binary为False,则message以utf8编码发送;二进制模式(binary=True)时,可发送任何字节码。

WebSocketHandler.close()

关闭WebSocket连接。

WebSocketHandler.check_origin(origin)

判断源origin,对于符合条件(返回判断结果为True)的请求源origin允许其连接,否则返回403。可以重写此方法来解决WebSocket的跨域请求(如始终return True)

2. 前端JavaScript编写

在前端JS中使用WebSocket与服务器通讯的常用方法如下:

1
2
3
4
5
6
7
var ws = new WebSocket("ws://127.0.0.1:8888/websocket"); // 新建一个ws连接
ws.onopen = function() {  // 连接建立好后的回调
   ws.send("Hello, world");  // 向建立的连接发送消息
};
ws.onmessage = function (evt) {  // 收到服务器发送的消息后执行的回调
   alert(evt.data);  // 接收的消息内容在事件参数evt的data属性中
};

  

3. 在线聊天室的小Demo

后端代码 server.py

复制代码
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
import os,json
import datetime

from tornado.web import RequestHandler
from tornado.options import define, options
from tornado.websocket import WebSocketHandler

class IndexHandler(RequestHandler):
    def get_current_user(self):
        '''
        重写RequestHandler类中的get_current_user方法,用来判断当前是否是登录状态,请求中所有被@tornado.web.authenticated 装饰的方法,都需要此方法返回值不为None,否则给与403拒绝
        :return: 用户名或者None . 为None判断为非法请求,POST 时Tornado进行403禁止访问 ;GET 时 302 重定向到/login
        '''
        user = self.get_argument(name='username',default='None')
        if user and user != 'None':
            print('IndexHandler类 get_current_user获取到用户:',user)
            return user

    @tornado.web.authenticated   #确认请求合法 依赖于get_current_user(self):函数的返回值作为判断请求是否合法
    def get(self):
        print("IndexHandler 收到GET请求")
        self.render("online_index.html",current_user=self.current_user)

    @tornado.web.authenticated
    def post(self, *args, **kwargs):
        print('IndexHandler 收到POST请求')
        self.render("online_index.html", current_user=self.current_user)

class LoginHandler(RequestHandler):
    def get(self, *args, **kwargs):
        '''
        处理输入昵称界面get请求
        :param args:
        :param kwargs:
        :return:
        '''
        cookie_value = self.get_secure_cookie('count')
        print('cookie_value :', cookie_value)
        count = int(cookie_value) + 1 if cookie_value else 1
        self.set_secure_cookie("count", str(count))  # 设置一个带签名和时间戳的cookie,防止cookie被伪造。

        #使用ajax方法做的前端
        # self.render('login_use_ajax.html')
        #使用form表单提交数据 的前端
        self.render('login_use_form.html')
    def post(self, *args, **kwargs):
        '''
        暂时用不到
        :param args:
        :param kwargs:
        :return:
        '''
        pass

 # 继承tornado.websocket.WebSocketHandler,只处理WS协议的请求
class ChatHandler(WebSocketHandler):
    def get_current_user(self):
        user = self.get_argument(name='username',default='None')
        if user and user != 'None':
            return user

    users = set()  # 用来存放在线用户的容器
    @tornado.web.authenticated
    def open(self):
        print('收到新的WebSocket连接')
        self.users.add(self)  # 建立连接后添加用户到容器中
        for u in self.users:  # 向已在线用户发送消息
            u    .write_message(u"[%s]-[%s]-%s 进入聊天室" % (
    self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), self.current_user))

    def on_message(self, message):
        message = json.loads(message)
        print(type(message),message)
        for u in self.users:  # 向在线用户广播消息
            u.write_message(u"[%s]-[%s]-说:<br> &nbsp&nbsp&nbsp&nbsp%s" % ( datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), self.current_user,message.get('msg')))

    def on_close(self):
        self.users.remove(self) # 用户关闭连接后从容器中移除用户
        for u in self.users:
            u.write_message(u"[%s]-[%s]-%s 离开聊天室" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),self.current_user))

    def check_origin(self, origin):
        return True  # 允许WebSocket的跨域请求

if __name__ == '__main__':
    tornado.options.parse_command_line()    #允许命令行启动程序

    app = tornado.web.Application([         #定义处理web请求的应用程序
            (r"/", IndexHandler),
            (r"/login", LoginHandler),
            (r"/chat", ChatHandler),        # 处理WebSocket协议传输的数据
        ],
     websocket_ping_interval = 5,     # WebSocket ping探活包发送间隔秒数 static_path
= os.path.join(os.path.dirname(__file__), "statics"), #配置应用程序前端所需静态文件目录 template_path = os.path.join(os.path.dirname(__file__), "templates"), #配置html文件路径 login_url='/login', #配置登录url xsrf_cookies=True, #,防止跨站请求攻击,在post请求中起效, cookie_secret="2hcicVu+TqShDpfsjMWQLZ0Mkq5NPEWSk9fi0zsSt3A=", # 安全cookie用预置秘钥 base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes) debug = True #配置调试级别 ) http_server = tornado.httpserver.HTTPServer(app) #将应用处理逻辑 传递给HTTPServer 服务 define("port", default=8000, type=int) #设置一个监听地址 http_server.listen(options.port) #配置监听地址到 HTTPServe tornado.ioloop.IOLoop.current().start() #启动应用
复制代码

前端代码online_index.html

复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>聊天室</title>
</head>
<body>
    <div id="contents" style="height:500px;overflow:auto; border:green solid 2px"></div>
    <div>
        <textarea id="msg" style="width: 260px;height: 120px" placeholder="输入要发送的内容"></textarea>
        <input id="send_msg" type="button" href="javascript:;" onclick="sendMsg()" value="发送">
    </div>
    <script src="{{static_url('js/jquery.min.js')}}"></script>
    <script type="text/javascript">

        //创建ws协议连接,创建出来后就已经和服务端建立连接
        var ws = new WebSocket("ws://127.0.0.1:8000/chat?username={{current_user}}");

        //收到ws消息的时候在div对话框里面增加一行文字
        ws.onmessage = function(recv) {
            $("#contents").append("<p>" + recv.data + "</p>");
        };

        var username = "{{current_user}}";
/////////////////////////监控按下回车键动作//////////////////////////
//         $("#send_msg").keypress(
//             function () {
//                 console.log('发送消息');
//                 sendMsg();
//             }
//         )
///////////////////////////////////////////////////////////////////////
        function sendMsg() {
            $('#msg').val($('#msg').val().trim());//去除输入前后的空格
            var msg = $("#msg").val();
            if ( msg && msg.length >0) {
                data = {
                    // username:username,
                    msg:msg
                };
                ws.send(JSON.stringify(data));
                $("#msg").val("");
            }else {
                alert('内容不许为空')
            }
        }
    </script>
</body>
</html>
复制代码

前端login_use_form登录界面代码

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录聊天室</title>
    <script src="{{static_url('js/jquery.min.js')}}"></script>
</head>
<body>
    <!--使用form表单获取登录页-->
    <form action="/" method="post" onsubmit="return check_user()">
        {% module xsrf_form_html() %}
    <input id="username" name="username" placeholder="配置昵称,即可聊天">
    <input id="conform_username" type="submit" value="确认昵称"  >
    </form>

    <script>
        function check_user(){
            $('#username').val($('#username').val().trim());//去除输入前后的空格
            var username = $('#username').val();
            if ( !username ){
                alert('昵称不能为空');
                return false;
            }else {
                alert('昵称OK');
                return true
            }
        }
    </script>

</body>
</html>
复制代码

 

posted on   zhangmingda  阅读(5626)  评论(0编辑  收藏  举报

编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示