119 websocket 群聊 单聊 websocket的握手 加密解密
主要内容: https://www.cnblogs.com/nnngu/p/9347635.html
1 使用websocket实现一个简单的收发机制
from geventwebsocket.handler import WebSocketHandler from geventwebsocket.websocket import WebSocket from gevent.pywsgi import WSGIServer from flask import Flask, request, render_template app = Flask(__name__) @app.route("/ws") def ws(): user_socket = request.environ.get("wsgi.websocket") # print(user_socket) while 1: msg = user_socket.receive() print(msg) try: user_socket.send(msg) except: return if __name__ == '__main__': http_serv = WSGIServer(("0.0.0.0",5000), app, handler_class=WebSocketHandler) http_serv.serve_forever()
前端代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta http-equiv="content-Type" charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> </head> <body> </body> <script type="application/javascript"> // <!--新建一个websocket的连接--> var ws = new WebSocket("ws://127.0.0.1:5000/ws"); //当打开的时候加载 // ws.onopen = function(){ // alert("hello") // }; 接收后端穿过来的信息 ws.onmessage = function (ws_status) { console.log(ws_status.data) }; </script> </html>
2 使用websocket实现群聊
后端代码:
from geventwebsocket.handler import WebSocketHandler from geventwebsocket.websocket import WebSocket from gevent.pywsgi import WSGIServer from flask import Flask, request, render_template app = Flask(__name__) user_socket_list = [] # type:list @app.route("/ws") def ws(): user_socket = request.environ.get("wsgi.websocket") # type:WebSocket if user_socket: user_socket_list.append(user_socket) while 1: msg = user_socket.receive() print(msg) for usocket in user_socket_list: if user_socket == usocket: continue try: usocket.send(msg) except: continue @app.route('/') def index(): return render_template("ws群聊.html") if __name__ == '__main__': http_serv = WSGIServer(("0.0.0.0", 5000), app, handler_class=WebSocketHandler) http_serv.serve_forever()
前端代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta http-equiv="content-Type" charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> </head> <body background="/static/1.jpg"> <p>发送的内容: <input type="text" id="message"> <button onclick="send_msg()">发送信息</button></p> <div id="msg_list"> {# <img src="/static/1.jpg" alt="" style="height: 400px; height: 400px">#} </div> </body> <script type="application/javascript"> // <!--新建一个websocket的连接--> var ws = new WebSocket("ws://192.168.12.66:5000/ws"); //当打开的时候加载 // ws.onopen = function(){ // alert("hello") // }; {#接收服务器端传过来的信息#} ws.onmessage = function (ws_status) { console.log(ws_status.data); {#创建一个标签#} var ptag = document.createElement("p"); {#填充里面的内容#} ptag.innerText = ws_status.data; {#把内容加入到信息列表中#} document.getElementById("msg_list").appendChild(ptag) }; function send_msg() { var msg = document.getElementById("message").value; var ptag = document.createElement("p"); ptag.style.cssText = "text-align: right"; ptag.innerText = msg; document.getElementById("msg_list").appendChild(ptag); ws.send(msg) } </script> </html>
3 使用websocket实现单聊
4 webocket的握手信息
a : websocket协议包含连个部分: 一部分是握手, 一部分是数据传输
b : 服务端的消息如何发送个客户端:
long long ago~ 服务端想主动的push消息给客户端(比如聊天室的实时收发),这是不可能的,但是,我们可以通过ajax轮询和long poll技术
制造一个服务端给客户端主动push消息的假象
ajax轮询:: 让浏览器隔几秒就发送一次请求, 询问服务器是否有新的消息.
缺点:大大的增加了服务端的负载, 且速度很慢
long poll :: long poll和ajax的原理都差不多, 都是采用轮询的原理, 只不过来那个poll是采取阻塞的方式去轮询, 即客户端发起一个请求连接,这个连接会阻塞住, 知道服务端有了消息, 才会respnse给客户端.
缺点: 虽然降低了服务端的负载能力, 但是需要服务端有很高的并发能力.
websocket解决了服务器与客户端的全双工通信的问题. 解决了服务器的被动性, 当服务端升级后, 服务端可以主动推送信息给客户端.
c : websocket的握手
原理: http服务器识别websocket的方式首先是判断http头中的connection和upgrade头, 如果connectin是uprgrade, 且upgrade头是websocket, 则可以确定是webscket请求,这时候要进行websocket的握手处理, 通过Sec-WebSocket-Key的设置进行一些计算返回Sec-WebSocket-Accept的响应头. Sec-WebSocket-Key是客户端随机生成并进行base64的字符串,他的原始内容服务器并不关心, 服务器需要这个字符串与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接, 然后对着个拼接好的字符串进行sha1运算, 再进行base64编码, 为响应头Sec-WebSocket-Accept的值
Sec-WebSocket-Key的作用: 客户端将这个key发给服务器, 服务器对这个key进行处理返回给客户端, 客户端根据这个key是否正确来判断是否建立连接.
代码:
import socket, base64, hashlib sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 9527)) sock.listen(5) # 获取客户端socket对象 conn, address = sock.accept() # 获取客户端的【握手】信息 data = conn.recv(1024) print(data) """ b'GET / HTTP/1.1\r\n Host: 127.0.0.1:9527\r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\n Accept-Encoding: gzip, deflate\r\n Sec-WebSocket-Version: 13\r\n Origin: http://localhost:63342\r\n Sec-WebSocket-Extensions: permessage-deflate\r\n Sec-WebSocket-Key: 3vbd1/UIZdjSZJ+LmF9+Wg==\r\n Connection: keep-alive, Upgrade\r\n Cookie: csrftoken=XHlcBVZmyRl833qU4WZ0YGPI9QFQsTi0L1TlOrz6cwDgM8EJeHyY2pxhUyg4RNuD\r\n Pragma: no-cache\r\n Cache-Control: no-cache\r\n Upgrade: websocket\r\n\r\n' """ # # # magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11 magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' # # def get_headers(data): header_dict = {} header_str = data.decode("utf8") for i in header_str.split("\r\n"): if str(i).startswith("Sec-WebSocket-Key"): header_dict["Sec-WebSocket-Key"] = i.split(":")[1].strip() return header_dict # {Sec-WebSocket-Key : 3vbd1/UIZdjSZJ+LmF9+Wg==} headers = get_headers(data) # 提取请求头信息 # 对请求头中的sec-websocket-key进行加密 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://127.0.0.1:9527\r\n\r\n" value = headers['Sec-WebSocket-Key'] + magic_string # value = 3vbd1/UIZdjSZJ+LmF9+Wg==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 # print(value) ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest()) response_str = response_tpl % (ac.decode('utf-8')) # 响应【握手】信息 conn.send(response_str.encode("utf8")) # while True: msg = conn.recv(8096) print(msg)
html中的代码:
<body> </body> <script type="application/javascript"> var ws = new WebSocket("ws://127.0.0.1:9527") </script> </html>
d: 加密
hashstr = b'\x81\x89ER\x99v\xa1\xef9\x93\xe0\xef|\xe3\xcf' # 将第二个字节也就是 \x8er 进行与127进行位运算 payload = hashstr[1]&127 print(payload) if payload == 127: extend_payload_len = hashstr[2:10] mask = hashstr[10:14] # 钥匙 decoded = hashstr[14:] # 数据 # 当位运算结果等于127时,则第3-10个字节为数据长度 # 第11-14字节为mask 解密所需字符串 # 则数据为第15字节至结尾 if payload == 126: extend_payload_len = hashstr[2:4] mask = hashstr[4:8] decoded = hashstr[8:] # 当位运算结果等于126时,则第3-4个字节为数据长度 # 第5-8字节为mask 解密所需字符串 # 则数据为第9字节至结尾 if payload <= 125: extend_payload_len = None mask = hashstr[2:6] # \xf0\x89\xae\x06 decoded = hashstr[6:] # \x95\xe5\xc2\x01\xd0\xfe\xc1\x1c\x9c\xed # 当位运算结果小于等于125时,则这个数字就是数据的长度 # 第3-6字节为mask 解密所需字符串 # 则数据为第7字节至结尾 str_byte = bytearray()#[b"",b"",b""] for i in range(len(decoded)): byte = decoded[i] ^ mask[i % 4] str_byte.append(byte) print(str_byte.decode("utf8"))
e : 解密:
import struct msg_bytes = "hello".encode("utf8") token = b"\x81" length = len(msg_bytes) if length < 126: token += struct.pack("B", length) elif length == 126: token += struct.pack("!BH", 126, length) else: token += struct.pack("!BQ", 127, length) msg = token + msg_bytes print(msg)