应用Websocket协议实现聊天室功能

举报
孤寒者 发表于 2021/08/31 22:05:18 2021/08/31
【摘要】 @TOC 1.什么是 WebSocket (1)定义WebSocket一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。WebSocket API也被W3C定为标准。持久连接双向通讯能处理大量连接非阻塞(异步) (2)优点WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许...

@TOC

1.什么是 WebSocket

(1)定义

WebSocket一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。WebSocket API也被W3C定为标准。

  • 持久连接
  • 双向通讯
  • 能处理大量连接
  • 非阻塞(异步)

(2)优点

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。但也因为这,所以WebSocket就不那么安全,数据双向传输就几乎是全透明的了!

(3)和 HTTP 对比

传统 HTTP 客户端与服务器的请求-响应模式 对比 WebSocket 模式

在这里插入图片描述

(4)适用场景

实时响应的应用

  • 聊天室
  • 通知等

(注意——限制:
不是所有浏览器都支持)

2.WebSocket 关键方法

tornado 的 WebSocketHandler

  • open 客户端连接成功时,自动调用
  • on_message 客户端连发送消息时,自动调用
  • on_close 客户端关闭连接时,自动调用

3.本tornado项目中使用WebSocket

(1)准备一个聊天室的页面:

第一步:编写视图:

在handler文件中创建chat.py文件用于编写聊天室视图!

import tornado.websocket
from .main import AuthBaseHandler

class RoomHandler(AuthBaseHandler):
    """
    聊天室页面
    """
    def get(self,*args,**kwargs):
        self.render('room.html')

第二步:编写接口:(app.py中加入以下接口!)

在这里插入图片描述

第三步:编写前端页面:(新建templates/room.html文件并码如下code!)

{% extends 'base.html' %}
{% block title %}post page{% end %}
{% block content %}
I am room page<br>

<!--此div用来做聊天内容的显示-->
<div id="inbox" >

</div>

<!--此div用来做聊天内容的输入提交-->
<div id="input">
    <form action="#" method="post" id="messageform">

        <table>
            <tr>
                <td>
                    <input name="body" id="message" style="width: 500px">
                </td>
                <td style="padding-left: 5px">
                    <input type="submit" value="提交">
                </td>

            </tr>
        </table>

    </form>

</div>

{% end %}

测试接口——响应OK!

在这里插入图片描述

(2)使用WebSocket示例:

①在chat.py中创建一个类用来继承和改写tornado中用户实现WebSocket的类WebSocketHandler,用来做服务端——处理和响应WebSocket连接,即实现用户聊天逻辑

class ChatWsHandler(tornado.websocket.WebSocketHandler):
    """
    处理和响应websocket连接
    """
    def open(self,*args,**kwargs):
        # 客户端连接成功时,自动调用
        print('new ws connection:{}'.format(self))

    def on_close(self):
        # 客户端关闭连接时,自动调用
        print('close ws connection:{}'.format(self))

    def on_message(self,message):
        # 服务器端接收到客户端发送的信息自动调用
        print('got message:{}'.format(message))
        self.write_message(u'You said:'+ message)

②不要忘了在app.py中加入指定此视图的接口哦!
在这里插入图片描述
③测试——
浏览器访问此接口,但是这样是测试不了哦!
在这里插入图片描述

我们需要到浏览器去调用——步骤如下:

1.在浏览器的检查里使用console做调试,用来发送 WebSocket请求,下图是浏览器通过此命令发送给服务器端的信息:

  ws = new WebSocket('ws://ip:端口号/ws')

在这里插入图片描述

2.调试完成后,服务器会接收到并且返回实例的地址

在这里插入图片描述
3. 可以发送信息给服务端,因为我们继承重写的时候在服务器端打印了接收来自客户端的信息:

ws.send('hello message')   

在这里插入图片描述在这里插入图片描述
4. 浏览器关闭连接(当然你直接关闭浏览器也可以!)

ws.close() 		

在这里插入图片描述
在这里插入图片描述

  • ws.onmessage 接受服务端发来的信息
  • ws.send() 发信息给服务端
  • 其他 ws.onopen, ws.onerror

(3)聊天室的聊天功能的最终实现:

终极目标是:
为了可以进行聊天室的聊天功能展示 ,我们把room页面做为聊天室的最终界面:包括内容输入框和历史信息显示框。

第一步:战前准备

为了使浏览器访问此接口就建立WebSocket连接(上面那种用浏览器调试窗口连接也太LOW了吧!),我们在templates/room.html中调用一个JS文件(在此JS文件中建立连接!),操作如下:

①static/创建一个名为chat.js的文件:

var url = "ws://" + location.host + "/ws";
var ws =new WebSocket(url);
ws.onmessage = function (event) {
    $("#inbox").append(event.data)
};

②templates/room.html中引入:

{% block extra_script %}
<script src="{{ static_url('js/chat.js') }}" type="text/javascript"></script>
{% end %}

③测试——大功告成:
此时我们访问/room接口,会发现就已经和服务器建立了WebSocket连接!

在这里插入图片描述在这里插入图片描述

第二步:终极实现!

①websocket实现逻辑的改写(handlers/chat.py文件)

import uuid

import tornado.websocket
from .main import AuthBaseHandler

class RoomHandler(AuthBaseHandler):
    """
    聊天室页面
    """
    def get(self,*args,**kwargs):
        self.render('room.html',messages=ChatWsHandler.history)


class ChatWsHandler(tornado.websocket.WebSocketHandler):
    """
    处理和响应websocket连接
    """
    waiters = set()  # 等待接收信息的用户
    history = []     # 存放历史聊天信息

    def open(self,*args,**kwargs):
        # 客户端连接成功时,自动调用
        print('new ws connection:{}'.format(self))
        # 将用户加到连接中
        ChatWsHandler.waiters.add(self)

    def on_close(self):
        # 客户端关闭连接时,自动调用
        print('close ws connection:{}'.format(self))
        # 将用户从连接中关闭
        ChatWsHandler.waiters.remove(self)

    def on_message(self,message):
        # 服务器端接收到客户端发送的信息自动调用
        print('got message:{}'.format(message))

        # 将接收到的数据转换一下——解密json数据(chat.js文件中对message信息进行就转json操作)
        parsed = tornado.escape.json_decode(message)
        # 接收数据
        chat = {
            # 用户id要唯一哦!  所以用uuid库生成唯一的字符串
            'id': str(uuid.uuid4()),
            'body': parsed['body']
        }

        # 显示聊天内容的页面和连接
        message_html = {
            'html': tornado.escape.to_basestring(
                self.render_string('message.html', chat=chat)
            ),
            'id': chat['id']
        }
        # 将用户发送的聊天的信息跟新进消息历史记录里
        ChatWsHandler.history.append(message_html)
        # 给每个等待的用户发送新的历史聊天记录信息
        ChatWsHandler.send_updates(message_html)

    @classmethod
    def send_updates(cls, message_html):
        """给每个等待接收的用户发送新信息"""
        for w in ChatWsHandler.waiters:
            w.write_message(message_html)

②前端聊天室界面的完善(templates/room.html文件)

{% extends 'base.html' %}
{% block title %}post page{% end %}
{% block content %}
I am room page<br>

<!--此div用来做聊天内容的显示-->
<div id="inbox" >

    {% for message in messages %}
        {% raw message['html'] %}
    {% end %}

</div>

<!--此div用来做聊天内容的输入提交-->
<div id="input">
    <form action="#" method="post" id="messageform">

        <table>
            <tr>
                <td>
                    <input name="body" id="message" style="width: 500px">
                </td>
                <td style="padding-left: 5px">
                    <input type="submit" value="提交">
                </td>

            </tr>
        </table>

    </form>

</div>

{% end %}

{% block extra_script %}
    <script src="{{ static_url('js/chat.js') }}" type="text/javascript"></script>
{% end %}

③js文件的完善——修改chat.js代码使之可以进行提交数据和显示聊天数据(static/js/chat.js文件)

$(document).ready(function() {
    if (!window.console) window.console = {};
    if (!window.console.log) window.console.log = function() {};

    $("#messageform").on("submit", function() {  // 点击提交时执行
        newMessage($(this));
        return false;
    });
    $("#messageform").on("keypress", function(e) {  // 回车提交时执行
        if (e.keyCode == 13) {
            newMessage($(this));
            return false;
        }
    });
    $("#message").select();
    updater.start();   // 开始 WebSocket
});

function newMessage(form) {     // 发送新消息给服务器
    var message = form.formToDict();
    updater.socket.send(JSON.stringify(message));
    $("input[name='body']").val("").select();
}

jQuery.fn.formToDict = function() {     // 对获取的聊天数据进行一些转换操作
    var fields = this.serializeArray();
    var json = {};
    for (var i = 0; i < fields.length; i++) {
        json[fields[i].name] = fields[i].value;
    }
    if (json.next) delete json.next;
    return json;
};

var updater = {
    socket: null,

    start: function() {
        var url = "ws://" + location.host + "/ws";
        updater.socket = new WebSocket(url);  // 初始化 WebSocket
        updater.socket.onmessage = function(event) {  // 获取到服务器的信息时响应
            updater.showMessage(JSON.parse(event.data));
        }
    },

    showMessage: function(message) {
        var existing = $("#m" + message.id);
        if (existing.length > 0) return;
        var node = $(message.html);
        node.hide();
        $("#inbox").append(node);  // 添加消息 div 到页面
        node.slideDown();          // 滑动及时显示聊天信息
    }
};

实现效果:

多个用户访问都可显示所有聊天历史记录信息。但是只要所有人退出,再进入聊天室页面,历史记录信息就情况了!
在这里插入图片描述

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。