WebSocket
websocket原理
1、服务端运行,等待客户端连接
2、某某来连接,服务端同意
3、某某立即发送一个握手信息
b'GET /xxxx HTTP/1.1\r\n Host: 127.0.0.1:8002\r\n Connection: Upgrade\r\n Pragma: no-cache\r\n Cache-Control: no-cache\r\n Upgrade: websocket\r\n Origin: http://localhost:63342\r\n Sec-WebSocket-Version: 13\r\n User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36\r\n Accept-Encoding: gzip, deflate, br\r\n Accept-Language: zh-CN,zh;q=0.9\r\n Cookie: Hm_lvt_d214947968792b839fd669a4decaaffc=1550556279\r\n Sec-WebSocket-Key: MdPqInjP3oSeusycSwAPRg==\r\n Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n \r\n'
4、服务端接收握手信息后需要对数据进行加密
-- 'MdPqInjP3oSeusycSwAPRg==' + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
-- sha1加密
-- base64加密
-- 加密后返回结果给客户端
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade:websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "WebSocket-Location: ws://%s%s\r\n\r\n" response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url']) conn.send(bytes(response_str, encoding='utf-8'))
5、双方进行通信
a、客户端给服务端发消息
读取第二个字节的后7位
127:10(头部),4(MASK KEY),数据包
126:4(头部),4(MASK KEY),数据包
125:2(头部),4(MASK KEY),数据包
b、服务端给客户端发送消息
def send_msg(conn, msg_bytes): """ WebSocket服务端向客户端发送消息 :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept() :param msg_bytes: 向客户端发送的字节 :return: """ import struct token = b"\x81" length = len(msg_bytes) if length < 126: token += struct.pack("B", length) elif length <= 0xFFFF: token += struct.pack("!BH", 126, length) else: token += struct.pack("!BQ", 127, length) msg = token + msg_bytes conn.send(msg) return True
基于Python实现简单示例
服务端:server.py
import socket import base64 import hashlib def get_headers(data): """ 将请求头格式化成字典 :param data: :return: """ header_dict = {} data = str(data, encoding='utf-8') # for i in data.split('\r\n'): # print(i) header, body = data.split('\r\n\r\n', 1) header_list = header.split('\r\n') for i in range(0, len(header_list)): if i == 0: if len(header_list[i].split(' ')) == 3: header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ') else: k, v = header_list[i].split(':', 1) header_dict[k] = v.strip() return header_dict def send_msg(conn, msg_bytes): """ WebSocket服务端向客户端发送消息 :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept() :param msg_bytes: 向客户端发送的字节 :return: """ import struct token = b"\x81" length = len(msg_bytes) if length < 126: token += struct.pack("B", length) elif length <= 0xFFFF: token += struct.pack("!BH", 126, length) else: token += struct.pack("!BQ", 127, length) msg = token + msg_bytes conn.send(msg) return True sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 8002)) sock.listen(5) # 1、等待客户连接 conn, address = sock.accept() # 2、接收验证消息 ''' b'GET /xxxx HTTP/1.1\r\n Host: 127.0.0.1:8002\r\n Connection: Upgrade\r\n Pragma: no-cache\r\n Cache-Control: no-cache\r\n Upgrade: websocket\r\n Origin: http://localhost:63342\r\n Sec-WebSocket-Version: 13\r\n User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36\r\n Accept-Encoding: gzip, deflate, br\r\n Accept-Language: zh-CN,zh;q=0.9\r\n Cookie: Hm_lvt_d214947968792b839fd669a4decaaffc=1550556279\r\n Sec-WebSocket-Key: MdPqInjP3oSeusycSwAPRg==\r\n Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n \r\n' ''' data = conn.recv(8096) headers = get_headers(data) # 3、对数据进行加密 magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' # 固定加密值 value = headers['Sec-WebSocket-Key'] + magic_string # 最终加密值 ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest()) # 固定加密算法 # 4、将加密后的值返回给客户端 response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade:websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "WebSocket-Location: ws://%s%s\r\n\r\n" response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url']) conn.send(bytes(response_str, encoding='utf-8')) # 5、接收客户端发送的消息 while True: # 获取客户端发送的数据【解包】 try: info = conn.recv(8096) except Exception as e: info = None if not info: break payload_len = info[1] & 127 if payload_len == 126: extend_payload_len = info[2:4] mask = info[4:8] decoded = info[8:] elif payload_len == 127: extend_payload_len = info[2:10] mask = info[10:14] decoded = info[14:] else: extend_payload_len = None mask = info[2:6] decoded = info[6:] bytes_list = bytearray() for i in range(len(decoded)): chunk = decoded[i] ^ mask[i % 4] bytes_list.append(chunk) body = str(bytes_list, encoding='utf-8') # 向客户端发送数据【封包】 send_msg(conn, body.encode('utf-8')) sock.close()
客户端:client.html
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></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="content"></div> <script type="text/javascript"> var socket = new WebSocket("ws://127.0.0.1:8002/xxxx"); socket.onopen = function () { /* 与服务器端连接成功后,自动执行 */ var newTag = document.createElement('div'); newTag.innerHTML = "【连接成功】"; document.getElementById('content').appendChild(newTag); }; socket.onmessage = function (event) { /* 服务器端向客户端发送数据时,自动执行 */ var response = event.data; var newTag = document.createElement('div'); newTag.innerHTML = response; document.getElementById('content').appendChild(newTag); }; socket.onclose = function (event) { /* 服务器端主动断开连接时,自动执行 */ var newTag = document.createElement('div'); newTag.innerHTML = "【关闭连接】"; document.getElementById('content').appendChild(newTag); }; function sendMsg() { var txt = document.getElementById('txt'); socket.send(txt.value); txt.value = ""; } function closeConn() { socket.close(); var newTag = document.createElement('div'); newTag.innerHTML = "【关闭连接】"; document.getElementById('content').appendChild(newTag); } </script> </body> </html>
基于flask的示例

主要有两个文件:1、服务端(flask_websoket.py)2、客户端(index.html)
服务端:flask_websoket.py
from flask import Flask,render_template,request from geventwebsocket.handler import WebSocketHandler from gevent.pywsgi import WSGIServer import json app = Flask(__name__) # 用户数据 USER_DIC = { '1':{'name':'大哥','count':0}, '2':{'name':'二哥','count':0}, '3':{'name':'小弟','count':0}, } # websocket连接池 WEBSOCKET_LIST = [] @app.route('/') def index(): return render_template('index.html',userdic = USER_DIC) @app.route('/message') def message(): ws = request.environ.get('wsgi.websocket') if not ws: print('http') return 'HTTP协议' # 把每个人的连接存起来 WEBSOCKET_LIST.append(ws) while True: # 等待用户发送消息并接收 uid = ws.receive() # 如果关闭了连接 if not uid: # 移除连接 WEBSOCKET_LIST.remove(ws) # 关闭连接 ws.close() # 跳出循环 break old_count = USER_DIC[uid]['count'] new_count = old_count + 1 USER_DIC[uid]['count'] = new_count # 给所有用户返回最新数据 for client in WEBSOCKET_LIST: client.send(json.dumps({'uid':uid,'count':new_count})) return 'ok' if __name__ == '__main__': http_server = WSGIServer(('127.0.0.1',5000),app,handler_class=WebSocketHandler) http_server.serve_forever()
客户端:index.html
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <h1>投票系统</h1> <div> <ul> {% for k,v in userdic.items()%} <li onclick="goodfive({{ k }})" id = uid_{{ k }}>{{ v.name }}<span>{{ v.count }}</span></li> {% endfor %} </ul> </div> <script src="{{ url_for('static',filename='js/jquery-3.1.1.js')}}"></script> <script type="text/javascript"> var socket = new WebSocket("ws://127.0.0.1:5000/message"); socket.onmessage = function (event) { /* 服务器端向客户端发送数据时,自动执行 */ var response = JSON.parse(event.data); $('#'+'uid_'+response.uid).find('span').text(response.count); }; socket.onclose = function (event) { /* 服务器端主动断开连接时,自动执行 */ console.log('关闭连接'); }; function goodfive(uid) { socket.send(uid) } </script> </body> </html>
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></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="content"></div> <script type="text/javascript"> var socket = new WebSocket("ws://127.0.0.1:8003/chatsocket"); socket.onopen = function () { /* 与服务器端连接成功后,自动执行 */ var newTag = document.createElement('div'); newTag.innerHTML = "【连接成功】"; document.getElementById('content').appendChild(newTag); }; socket.onmessage = function (event) { /* 服务器端向客户端发送数据时,自动执行 */ var response = event.data; var newTag = document.createElement('div'); newTag.innerHTML = response; document.getElementById('content').appendChild(newTag); }; socket.onclose = function (event) { /* 服务器端主动断开连接时,自动执行 */ var newTag = document.createElement('div'); newTag.innerHTML = "【关闭连接】"; document.getElementById('content').appendChild(newTag); }; function sendMsg() { var txt = document.getElementById('txt'); socket.send(txt.value); txt.value = ""; } function closeConn() { socket.close(); var newTag = document.createElement('div'); newTag.innerHTML = "【关闭连接】"; document.getElementById('content').appendChild(newTag); } </script> </body> </html>
基于Tornado实现的聊天室示例
#!/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): # 用户存储当前聊天室用户 waiters = set() # 用于存储历时消息 messages = [] def open(self): """ 客户端连接成功时,自动执行 :return: """ ChatHandler.waiters.add(self) uid = str(uuid.uuid4()) self.write_message(uid) for msg in 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(message) for client in ChatHandler.waiters: content = client.render_string('message.html', **msg) client.write_message(content) def on_close(self): """ 客户端关闭连接时,,自动执行 :return: """ ChatHandler.waiters.remove(self) 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()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Python聊天室</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(); }); var wsUpdater = { socket: null, uid: null, start: function() { var url = "ws://127.0.0.1:8888/chat"; wsUpdater.socket = new WebSocket(url); wsUpdater.socket.onmessage = function(event) { console.log(event); if(wsUpdater.uid){ wsUpdater.showMessage(event.data); }else{ wsUpdater.uid = event.data; } } }, showMessage: function(content) { $('#container').append(content); } }; function sendMsg() { var msg = { uid: wsUpdater.uid, message: $("#txt").val() }; wsUpdater.socket.send(JSON.stringify(msg)); } </script> </body> </html>

浙公网安备 33010602011771号