首页 > Web开发 > 详细

Tornado WebSocket简单聊天

时间:2019-08-29 17:21:38      阅读:168      评论:0      收藏:0      [点我收藏+]

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="width:60%;">
        <div>
            聊天室个人登录
        </div>
        <div>
            <form method="post" action="/login" style="width: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
 

 

Tornado WebSocket简单聊天

原文:https://www.cnblogs.com/cj8988/p/11430858.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!