应用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(); // 滑动及时显示聊天信息
}
};
实现效果:
多个用户访问都可显示所有聊天历史记录信息。但是只要所有人退出,再进入聊天室页面,历史记录信息就情况了!
- 点赞
- 收藏
- 关注作者
评论(0)