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"
posted @ 2019-11-29 15:04  阿浪阿浪  阅读(257)  评论(0编辑  收藏  举报