理论:
它基于TCP传输协议,并复用HTTP的握手通道,属于应用层协议
Socket 是传输控制层协议,WebSocket 是应用层协议。
- 建立连接
- 交换数据
- 数据帧格式
- 维持连接
1.建立连接
首先,客户端发起协议升级请求。可以看到,采用的是标准的HTTP报文格式,且只支持GET
方法。[将数据放到socket里发送]
GET / HTTP/1.1 Host: localhost:8080 Origin: http://127.0.0.1:3000 Connection: Upgrade # 表示要升级协议 Upgrade: websocket # 表示要升级到websocket协议。 Sec-WebSocket-Version: 13 # 表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。 Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw== # 与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。
服务端返回内容如下,状态代码101
表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。
HTTP/1.1 101 Switching Protocols Connection:Upgrade Upgrade: websocket Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU= #Sec-WebSocket-Accept由Sec-WebSocket-Key计算得出: toBase64( sha1( Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ) )
magic_string是什么?【固定值】: 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
2.交换数据
一旦WebSocket客户端、服务端建立连接后,后续的操作都是基于数据帧的传递。
WebSocket根据opcode
来区分操作的类型。比如0x8
表示断开连接,0x0
-0x2
表示数据交互。
3.数据帧格式
略
4.维持连接
发送心跳,ping pong
具体使用:
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
以下 API 用于创建 WebSocket 对象。
示例:web聊天室应用
原理,一个websocket是一个用户,服务端可以将用户A的消息推送给所有用户,服务端维护一个用户列表和一个历史消息列表。
访问http://127.0.0.1:8888/ 即打开群聊窗口,显示所有历史消息。用户发送的消息能被所有客户端实时看到。用户关闭浏览器,服务端用户列表-1
目录结构:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>web聊天室</title> </head> <body> <div> <input type="text" id="txt"/> <input type="button" id="btn" value="提交" onclick="sendMsg();"/> <input type="button" id="close" value="关闭连接" onclick="closeConn();"/> </div> <div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;"> </div> <script src="/static/jquery-2.1.4.min.js"></script> <script type="text/javascript"> $(function () { wsUpdater.start(); //页面一加载就开始执行,目的是初始化websocket连接,让websocket处于就绪状态 }); var wsUpdater = { socket: null, uid: null, start: function() { var url = "ws://127.0.0.1:8888/chat"; // 定义websocket请求地址 wsUpdater.socket = new WebSocket(url); // 初始化websocket时传入url,建立websocket连接 // websocket对象的onmessage事件,收到消息触发,收到的消息存放在event.data中。刚建立连接就会收到服务端的UUID wsUpdater.socket.onmessage = function(event) { if(wsUpdater.uid){ wsUpdater.showMessage(event.data); }else{ wsUpdater.uid = event.data; //刚建立连接时,将服务器发来的UUID赋值给uid } } }, showMessage: function(content) { $('#container').append(content); }, stop:function () { console.log(wsUpdater.socket); wsUpdater.socket.stop(); } }; function sendMsg() { //点击发送按钮 var msg = { uid: wsUpdater.uid, message: $("#txt").val() }; wsUpdater.socket.send(JSON.stringify(msg)); } function closeConn() { wsUpdater.stop(); alert("关闭成功") } </script> </body> </html>
message.html >>>
<div style="border: 1px solid #dddddd;margin: 10px;"> <div>游客{{uid}}</div> <div style="margin-left: 20px;">{{message}}</div> </div>
#!/usr/bin/env python # -*- coding:utf-8 -*- import uuid import json import tornado.ioloop import tornado.web import tornado.websocket class IndexHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') class ChatHandler(tornado.websocket.WebSocketHandler): # 继承WebSocketHandler # 用户存储当前聊天室用户 waiters = set() # 用于存储历时消息 messages = [] def open(self): """ 客户端websocket连接成功时,自动执行 :return: """ ChatHandler.waiters.add(self) # websocket连一次就是一个新的self uid = str(uuid.uuid4()) self.write_message(uid) # 若成功建立连接,将uid发给来连接的客户端 print("一个新的websocket加入了。。uid已发给客户端", self) print("当前聊天室人数", len(ChatHandler.waiters)) for msg in ChatHandler.messages: # 刚连上,ChatHandler.messages为历史消息,直接推送给刚登陆进来的用户 content = self.render_string('message.html', **msg) self.write_message(content) def on_message(self, message): """ 客户端连发送消息时,自动执行 :param message: :return: """ msg = json.loads(message) ChatHandler.messages.append(msg) for client in ChatHandler.waiters: # 接收到消息,将消息推送给所有websocket用户 content = client.render_string('message.html', **msg) client.write_message(content) def on_close(self): """ 客户端关闭连接时,,自动执行 :return: """ ChatHandler.waiters.remove(self) print("当前聊天室人数", len(ChatHandler.waiters)) def run(): settings = { 'template_path': 'templates', 'static_path': 'static', } application = tornado.web.Application([ (r"/", IndexHandler), (r"/chat", ChatHandler), ], **settings) application.listen(8888) tornado.ioloop.IOLoop.instance().start() if __name__ == "__main__": run()
websocket实现打印后端执行进度
#!/usr/bin/env python # -*- coding:utf-8 -*- import time import json import tornado.ioloop import tornado.web import tornado.websocket class IndexHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') class ChatHandler(tornado.websocket.WebSocketHandler): # 继承WebSocketHandler def on_message(self, message): """ 客户端连发送消息时,自动执行 :param message: :return: """ msg = json.loads(message) self.write_message("收到你的请求了,开始将excel载入内存...") time.sleep(5) self.write_message("载入内存完成,开始在内存中遍历解析excel...") time.sleep(5) self.write_message("解析数据完成,开始入库...") time.sleep(5) self.write_message("入库完成,报送成功") self.close() def run(): settings = { 'template_path': 'templates', 'static_path': 'static', } application = tornado.web.Application([ (r"/", IndexHandler), (r"/chat", ChatHandler), ], **settings) application.listen(8888) tornado.ioloop.IOLoop.instance().start() if __name__ == "__main__": run()