websocket
1. websocket
```
基于http的一个协议。(握手环节发送和响应时,用http协议规定传递)
协议规定了浏览器和服务端创建连接之后,不断开,保持连接。相互之间可以基于连接进行收发消息。
websocket原理也了解过:
- 握手环节
- 加密解密环节
```
2. 原理
- 客户端向服务端发送随机字符串,在http的请求头中 `Sec-WebSocket-Key`
- 服务端接受到到随机字符串
- 随机字符串 + 魔法字符串
- sha1加密
- base64加密
- 放在响应头中给用户返回 `Sec-WebSocket-Accept`
- 客户端浏览器会进行校验,校验不通过:服务端不支持websocket协议。
- 客户端和服务端进行相互收发消息,数据加密和解密。
- 解密细节:
- 获取第二个字节的后7位,payload len
- 判断payload len的值
- = 127
```
2字节 + 8字节 + 4字节 + 数据
```
- = 126
```
2字节 + 2字节 + 4字节 + 数据
```
- <= 125
```
2字节 + 4字节 + 数据
```
- 使用masking key 对数据进行解密
3. websocket多对多,hello.py
import socket
import hashlib
import base64
def get_headers(data):
"""
将请求头格式化成字典
:param data:
:return:
"""
header_dict = {}
data = str(data, encoding='utf-8')
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 get_data(info):
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')
return body
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)
# 等待用户连接
conn, address = sock.accept()
# 握手环节
header_dict = get_headers(conn.recv(1024))
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
random_string = header_dict['Sec-WebSocket-Key']
value = random_string + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response = "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:8002\r\n\r\n"
response = response %ac.decode('utf-8')
print(response)
conn.send(response.encode('utf-8'))
# 接受数据
while True:
data = conn.recv(1024)
msg = get_data(data)
print(msg)
world.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="button" value="开始" onclick="startConnect();">
<script>
var ws = null;
function startConnect() {
// 1. 内部会先发送随机字符串
// 2. 内部会校验加密字符串
ws = new WebSocket('ws://127.0.0.1:8002')
}
</script>
</body>
</html>
4. 框架应用
django和flask框架,内部基于wsgi做的socket,默认他俩都不支持websocket协议,
只支持http协议。
- flask中应用
pip3 install gevent-websocket
- django中应用
pip3 install channels
5. Django自己的websocket:channels.layers方式
pip3 install channels
webchat
settings.py
ASGI_APPLICATION = "wechat.routing.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}
routing.py
from channels.routing import ProtocolTypeRouter, URLRouter
from django.conf.urls import url
from app01 import consumers
application = ProtocolTypeRouter({
'websocket': URLRouter([
url(r'^x1/$', consumers.ChatConsumer),
])
})
routing.py >>>>>>>>>>>>>session 版
from channels.routing import ProtocolTypeRouter, URLRouter
from django.conf.urls import url
from channels.sessions import CookieMiddleware,SessionMiddlewareStack
from apps.web import consumers
application = ProtocolTypeRouter({
'websocket': SessionMiddlewareStack(URLRouter([
url(r'^deploy/online/(?P<task_id>\d+)/$', consumers.DeployConsumer),
]))
})
获取session的方式
class DeployConsumer(WebsocketConsumer):
def websocket_connect(self, message):
self.accept()
# 获取session
username = self.scope['session']['username']
print(f'username>>>>>>>>>{username}')
# 默认不会帮你保存session
# self.scope['session']['x1'] = 123
# 保存session需要执行save方法
# self.scope['session'].save()
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
]
views.py
from django.shortcuts import render
def index(request):
return render(request,'index.html')
consumers.py:根据用户浏览器上的操作自动触发响应的方法
# 第一种方法:自己应用底层代码
from channels.exceptions import StopConsumer
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
# 项目启动,输入网址回车自动触发:websocket_connect
def websocket_connect(self, message):
""" websocket连接到来时,自动执行 """
print('有人来了')
async_to_sync(self.channel_layer.group_add)('22922192', self.channel_name)
self.accept()
# websocket浏览器给发消息时,自动触发此方法
def websocket_receive(self, message):
""" websocket浏览器给发消息时,自动触发此方法 """
print('接收到消息', message)
async_to_sync(self.channel_layer.group_send)('22922192', {
'type': 'xxx.ooo',
'message': message['text']
})
def xxx_ooo(self, event):
message = event['message']
self.send(message)
def websocket_disconnect(self, message):
""" 断开连接 """
print('客户端主动断开连接了')
async_to_sync(self.channel_layer.group_discard)('22922192', self.channel_name)
raise StopConsumer()
# 第二种:自己定义成自己看的懂的方式
class NewChatConsumer(WebsocketConsumer):
def connect(self):
print('有人来了')
async_to_sync(self.channel_layer.group_add)('22922192', self.channel_name)
self.accept()
def receive(self, text_data=None, bytes_data=None):
print('接收到消息', text_data)
async_to_sync(self.channel_layer.group_send)('22922192', {
'type': 'xxx.ooo',
'message': text_data
})
def xxx_ooo(self, event):
message = event['message']
self.send(message)
def disconnect(self, code):
print('客户端主动断开连接了')
async_to_sync(self.channel_layer.group_discard)('22922192', self.channel_name)
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Web聊天室:<span id="tips"></span></h1>
<div class="form">
<input id="txt" type="text" placeholder="请输入文字">
<input id="btn" type="button" value="发送" onclick="sendMessage();">
</div>
<div id="content">
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
var ws;
$(function () {
initWebSocket();
});
function initWebSocket() {
ws = new WebSocket("ws://127.0.0.1:8000/x1/");
ws.onopen = function(){
$('#tips').text('连接成功');
};
ws.onmessage = function (arg) {
var tag = document.createElement('div');
tag.innerHTML = arg.data;
$('#content').append(tag);
};
ws.onclose = function () {
ws.close();
}
}
function sendMessage() {
ws.send($('#txt').val());
}
</script>
</body>
</html>
6. websocket 单对单(consumers.py)
# 一对一收发消息:
from channels.exceptions import StopConsumer
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def websocket_connect(self, message):
""" websocket连接到来时,自动执行 """
print('有人来了')
self.accept()
def websocket_receive(self, message):
""" websocket浏览器给发消息时,自动触发此方法 """
print('接收到消息', message)
self.send(text_data='收到了')
# self.close()
def websocket_disconnect(self, message):
print('客户端主动断开连接了')
raise StopConsumer()
# settings.py
ASGI_APPLICATION = "wechat.routing.application"
希望你眼眸有星辰,心中有山海,从此以梦为马,不负韶华