• Tornado WebSocket简单聊天


    Tornado实现了对socket的封装:tornado.web.RequestHandler

    工程目录:

    1、主程序 manage.py

    import tornado.web
    import tornado.httpserver
    from tornado.options import define, options, parse_command_line
    
    from chat.views import IndexHandler, LoginHandler, ChatHandler
    from util.settings import TEMPLATE_PATH, STATIC_PATH
    
    define("port", default=8180, help='run on the port', type=int)
    
    def make_app():
        return tornado.web.Application(handlers=[
            (r'/', IndexHandler),
            (r'/login', LoginHandler),
            (r'/chat', ChatHandler),
        ],
            pycket={
                'engine': 'redis',
                'storage': {
                    'host': 'fot.redis.cache.net',
                    'port': 6379,
                    'password': 'yKigE3ZF0mGBSP4/M=',
                    'db_sessions': 5,
                    'db_notifications': 11,
                    'max_connections': 2 ** 31,
                },
                'cookies': {
                    'expires_days': 30,
                    'max_age': 100
                },
            },
            login_url='/login',
            template_path=TEMPLATE_PATH,
            static_path=STATIC_PATH,
            debug=True,
            cookie_secret='cqVJzSSjQgWzKtpHMd4NaSeEa6yTy0qRicyeUDIMSjo='
        )
    
    
    if __name__ == '__main__':
        tornado.options.parse_command_line()
        app = make_app()
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(options.port)
        tornado.ioloop.IOLoop.current().start()
    View Code

    2、配置 settings.py

    import os
    
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    TEMPLATE_PATH = os.path.join(BASE_DIR, 'templates')
    
    STATIC_PATH = os.path.join(BASE_DIR, 'static')

    3、聊天程序 views.py

    # -*- coding: utf-8 -*-
    import datetime
    import json
    
    import tornado.web
    import tornado.websocket
    from tornado.web import authenticated  # 导入装饰器
    from pycket.session import SessionMixin
    
    
    # 设置BaseHandler类,重写函数get_current_user
    class BaseHandler(tornado.web.RequestHandler, SessionMixin):
        def get_current_user(self):  # 前面有绿色小圆圈带个o,再加一个箭头表示重写
            current_user = self.session.get('user')  # 获取加密的cookie
            if current_user:
                return current_user
            return None
    
    
    # 基类
    class BaseWebSocketHandler(tornado.websocket.WebSocketHandler, SessionMixin):
        def get_current_user(self):
            current_user = self.session.get('user')
    
            if current_user:
                return current_user
            return None
    
    
    # 跳转
    class IndexHandler(BaseHandler):
        @authenticated  # 内置装饰器,检查是否登录
        def get(self):
            self.render('chat.html')
    
    
    class LoginHandler(BaseHandler):
        def get(self):
            self.render('index.html')  # 跳转页面带上获取的参数
    
        def post(self, *args, **kwargs):
            user = self.get_argument('nickname', '')
            if user:
    
                self.session.set('user', user)  # 设置加密cookie
                self.redirect('/')  # 跳转到之前的路由
            else:
                self.render('index.html')
    
    
    class ChatHandler(BaseWebSocketHandler):
        # 定义接收/发送聊天消息的视图处理类,继承自websocket的WebSocketHandler
        # 定义一个集合,用来保存在线的所有用户
    
        online_users = set()
    
        # 从客户端获取cookie信息
    
        # 重写open方法,当有新的聊天用户进入的时候自动触发该函数
        def open(self):
    
            # 新用户上线,加入集合
            self.online_users.add(self)
            # 将新用户加入的信息发送给所有用户
    
            for user in self.online_users:
                user.write_message('[%s]join room' % self.current_user)
    
        # 重写on_message方法,当聊天消息有更新时自动触发的函数
        def on_message(self, message):
            msgobj = {'msg': message}
    
            for user in self.online_users:
                msgobj['key'] = '%s-%s-sea: ' % (self.current_user, datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
                user.write_message(json.dumps(msgobj))
    
    
        # 重写on_close方法,当有用户离开时自动触发的函数
        def on_close(self):
            # 移除用户
            self.online_users.remove(self)
            for user in self.online_users:
                user.write_message('[%s]remove room' % self.current_user)
    
        # 重写check_origin方法, 解决WebSocket的跨域请求
        def check_origin(self, origin):
            return True
    View Code

    4、前端登录 index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>聊天室登录首页</title>
        <script src="../static/jquery-3.4.1.js"></script>
    </head>
    <body>
    <div>
        <div style="60%;">
            <div>
                聊天室个人登录
            </div>
            <div>
                <form method="post" action="/login" style="80%">
                    <p>昵称:<input type="text" placeholder="请输入昵称" name="nickname"></p>
                    <button type="submit">登录</button>
                </form>
            </div>
        </div>
    </div>
    </body>
    </html>
    View Code

    5、前端聊天室 chat.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title> WebSocket </title>
        <style>
            *{
                margin: 0;
                padding: 0;
            }
            .box{
                width: 800px;
                margin-left: auto;
                margin-right: auto;
                margin-top: 25px;
            }
            #text{
                width: 685px;
                height: 130px;
                border: 1px solid skyblue;
                border-radius: 10px;
                font-size: 20px;
                text-indent: 1em;
                resize:none;
                outline: none;
            }
            #text::placeholder{
                color: skyblue;
            }
            .btn{
                width: 100px;
                margin: -27px 0 0px 8px;
            }
            #messages{
                padding-left: 10px;
                font-size: 25px;
            }
            #messages li{
                list-style: none;
                color: #000;
                line-height: 30px;
                font-size: 18px;
    
            }
        </style>
    </head>
    <body>
        <div class="box">
            <div>
                <textarea id="text" placeholder="请输入您的内容"></textarea>
                <a href="javascript:WebSocketSend();" class="btn btn-primary">发送</a>
            </div>
            <ul id="messages">
            </ul>
        </div>
        <script src="../static/jquery-3.4.1.js"></script>
        <script type="text/javascript">
            var mes = document.getElementById('messages');
            var wsUrl = "ws://"+ window.location.host +"/chat";
            var Socket = '';
    
            if('WebSocket' in window){  /*判断浏览器是否支持WebSocket接口*/
                /*创建创建 WebSocket 对象,协议本身使用新的ws://URL格式*/
    
                createWebSocket();
            }else{
                /*浏览器不支持 WebSocket*/
                alert("您的浏览器不支持 WebSocket!");
            }
    
            function createWebSocket() {
                  try {
                    Socket = new WebSocket(wsUrl);
    
                    init();
                  } catch(e) {
                    console.log('catch');
                    reconnect(wsUrl); //调用心跳
                  }
            }
    
            function init() {
                /*连接建立时触发*/
                Socket.onopen = function () {
                    alert("连接已建立,可以进行通信");
    
                    heartCheck.start();  //调用心跳
                };
                /*客户端接收服务端数据时触发*/
                Socket.onmessage = function (ev) {
                    var received_msg = ev.data; /*接受消息*/
                    var jopmsg = '';
                    try {
                         received_msg = JSON.parse(received_msg);
                         console.log(received_msg['msg']);
    
                         if(received_msg['msg'] == '121')
                             jopmsg = '121';
    
                         received_msg = received_msg['key'] + received_msg['msg'];
                    }catch (e) {
    
                    }
    
                    //发送信息为121时为心跳,不记录到页面(只是个约定)
                    if(jopmsg !== '121'){
                        var aLi = "<li>" + received_msg + "</li>";
                        mes.innerHTML += aLi;
                    }
    
                    heartCheck.start();  //调用心跳
                };
                /*连接关闭时触发*/
                Socket.onclose = function () {
                    mes.innerHTML += "<br>连接已经关闭...";
    
                    reconnect(wsUrl);  //关闭连接重新连接
                };
            }
    
            function WebSocketSend() {
                /*form 里的Dom元素(input select checkbox textarea radio)都是value*/
                var send_msg = document.getElementById('text').value;
                //或者JQ中获取
                // var send_msg = $("#text").val();
                /*使用连接发送消息*/
                Socket.send(send_msg);
                $("#text").val('');
            }
    
            var lockReconnect = false;//避免重复连接
            function reconnect(url) {
                 if(lockReconnect) {
                     return true;
                  };
    
                  lockReconnect = true;
                  //没连接上会一直重连,设置延迟避免请求过多
                  setTimeout(function () {
                    createWebSocket(url);
                    lockReconnect = false;
                  }, 5000);
    
            }
    
            //心跳检测
            var heartCheck = {
                  timeout: 10000, //每隔三秒发送心跳
                  num: 3,  //3次心跳均未响应重连
                  timeoutObj: null,
                  serverTimeoutObj: null,
                  start: function(){
                    var _this = this;
                    var _num = this.num;
                    this.timeoutObj && clearTimeout(this.timeoutObj);
                    this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
                    this.timeoutObj = setTimeout(function(){
                          //这里发送一个心跳,后端收到后,返回一个心跳消息,
                          //onmessage拿到返回的心跳就说明连接正常
                          Socket.send("121"); // 心跳包
                          _num--;
                          //计算答复的超时次数
                          if(_num === 0) {
                               Socket.colse();
                          }
                    }, this.timeout)
                  }
            }
    
        </script>
    </body>
    </html>
    View Code

    6、运行效果: 输入 http://127.0.0.1:8180

    7、部署到线上参考:https://www.cnblogs.com/cj8988/p/11288892.html

    注 :nginx需要添加一个配置 (在 server {} 里添加下面配置)

        location /chat {
            proxy_pass http://tornados;
            proxy_http_version 1.1;
            proxy_connect_timeout 4s;                #配置点1
            proxy_read_timeout 120s;                  #配置点2,如果没效,可以考虑这个时间配置长一点
            proxy_send_timeout 120s;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";  
        }

    8、注意,由于nginx超时问题,过段时间websocket会自动断开,所有前端需要设置心跳。

    前端 chat.html 中   :

       //心跳检测
            var heartCheck = {
                  timeout: 10000, //每隔三秒发送心跳
                  num: 3,  //3次心跳均未响应重连
                  timeoutObj: null,
                  serverTimeoutObj: null,
                  start: function(){
                    var _this = this;
                    var _num = this.num;
                    this.timeoutObj && clearTimeout(this.timeoutObj);
                    this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
                    this.timeoutObj = setTimeout(function(){
                          //这里发送一个心跳,后端收到后,返回一个心跳消息,
                          //onmessage拿到返回的心跳就说明连接正常
                          Socket.send("121"); // 心跳包
                          _num--;
                          //计算答复的超时次数
                          if(_num === 0) {
                               Socket.colse();
                          }
                    }, this.timeout)
                  }
            }

    在需要的地方调用:

    heartCheck.start();


    参考文档:
      https://www.jianshu.com/p/93b1788f055c

        
      https://www.lishuaishuai.com/html/759.html

      https://www.cnblogs.com/cj8988/p/11288892.html
  • 相关阅读:
    Cocos2dx引擎10-事件派发
    IE无法打开internet网站已终止操作的解决的方法
    让程序在崩溃时体面的退出之Dump文件
    天将降大任于斯人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,行拂乱其所为,所以动心忍性,增益其所不能
    RapeLay(电车之狼R)的结局介绍 (隐藏结局攻略)
    cocos 3.0 一键打包android平台应该注意的细节
    Matlab画图-非常具体,非常全面
    linux和windows文件名称长度限制
    Javascript的DOM操作
    50个高端大气上档次的管理后台界面模板
  • 原文地址:https://www.cnblogs.com/cj8988/p/11430858.html
Copyright © 2020-2023  润新知