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()
View Code

  前端代码:

<!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>
View Code

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

Host: 127.0.0.1:9527

User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

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

Accept-Encoding: gzip, deflate

Sec-WebSocket-Version: 13

Origin: http://localhost:63342

Sec-WebSocket-Extensions: permessage-deflate

Sec-WebSocket-Key: 3vbd1/UIZdjSZJ+LmF9+Wg==

Connection: keep-alive, Upgrade

Cookie: csrftoken=XHlcBVZmyRl833qU4WZ0YGPI9QFQsTi0L1TlOrz6cwDgM8EJeHyY2pxhUyg4RNuD

Pragma: no-cache

Cache-Control: no-cache

Upgrade: websocket

'
"""
#
# # 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("
"):
        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
" 
               "Upgrade:websocket
" 
               "Connection: Upgrade
" 
               "Sec-WebSocket-Accept: %s
" 
               "WebSocket-Location: ws://127.0.0.1:9527

"

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)
View Code

    html中的代码:

<body>
</body>
<script type="application/javascript">
    var ws = new WebSocket("ws://127.0.0.1:9527")
</script>
</html>

  d: 加密

hashstr = b'x81x89ERx99vxa1xef9x93xe0xef|xe3xcf'
# 将第二个字节也就是 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]  # xf0x89xaex06
    decoded = hashstr[6:]  # x95xe5xc2x01xd0xfexc1x1cx9cxed

# 当位运算结果小于等于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"))
View Code

  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)
View Code

    

原文地址:https://www.cnblogs.com/gyh412724/p/10156175.html