wepsocket 了解一下
WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。
本文将使用Python编写Socket服务端,一步一步分析请求过程!!!
简单个人定义:
1. 什么是websocket?
**是一套协议,协议规定了:
- 连接时需要握手
- 发送数据进行加密
- 连接之后不断开
**那些框架支持websocket?
- flask,gevent-websocket flask没有 要下载第三方组件 :gevent-websocket
- django,channel django没有 要下载第三方组件:channel
- torando框架自带
websocket的兼容性是他的缺点:
一些老版本浏览器 不支持
**应用场景?
实时响应页面时,可以使用websocket。
1. 启动服务端
import socket
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()
...
...
...
启动Socket服务器后,等待用户【连接】,然后进行收发数据。
2. 客户端连接
<script type="text/javascript">
var socket = new WebSocket("ws://127.0.0.1:8002/xxoo");
...
</script>
当客户端向服务端发送连接请求时,不仅连接还会发送【握手】信息,并等待服务端响应,至此连接才创建成功!
3. 建立连接【握手】
import socket 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)
# 获取客户端socket对象
conn, address = sock.accept()
# 获取客户端的【握手】信息
data = conn.recv(1024)
...
...
...
conn.send('响应【握手】信息')
请求和响应的【握手】信息需要遵循规则:
- 从请求【握手】信息中提取 Sec-WebSocket-Key
- 利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密
- 将加密结果响应给客户端
注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
请求【握手】信息为:
GET /chatsocket HTTP/1.1
Host: 127.0.0.1:8002
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
...
...
提取Sec-WebSocket-Key值并加密:
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 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()
data = conn.recv(1024)
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://%s%s\r\n\r\n"
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
# 响应【握手】信息
conn.send(bytes(response_str, encoding='utf-8'))
...
...
...
4.客户端和服务端收发数据
客户端和服务端传输数据时,需要对数据进行【封包】和【解包】。客户端的JavaScript类库已经封装【封包】和【解包】过程,但Socket服务端需要手动实现。
第一步:获取客户端发送的数据【解包】
info = conn.recv(8096)
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')
print(body)
基于Python实现解包过程(未实现长内容)
解包详细过程:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
The MASK bit simply tells whether the message is encoded. Messages from the client must be masked, so your server should expect this to be 1. (In fact, section 5.1 of the spec says that your server must disconnect from a client if that client sends an unmasked message.) When sending a frame back to the client, do not mask it and do not set the mask bit. We'll explain masking later. Note: You have to mask messages even when using a secure socket.RSV1-3 can be ignored, they are for extensions.
The opcode field defines how to interpret the payload data: 0x0 for continuation,
0x1for text (which is always encoded in UTF-8),0x2for binary, and other so-called "control codes" that will be discussed later. In this version of WebSockets,0x3to0x7and0xBto0xFhave no meaning.The FIN bit tells whether this is the last message in a series. If it's 0, then the server will keep listening for more parts of the message; otherwise, the server should consider the message delivered. More on this later.
Decoding Payload Length
To read the payload data, you must know when to stop reading. That's why the payload length is important to know. Unfortunately, this is somewhat complicated. To read it, follow these steps:
- Read bits 9-15 (inclusive) and interpret that as an unsigned integer. If it's 125 or less, then that's the length; you're done. If it's 126, go to step 2. If it's 127, go to step 3.
- Read the next 16 bits and interpret those as an unsigned integer. You're done.
- Read the next 64 bits and interpret those as an unsigned integer (The most significant bit MUST be 0). You're done.
Reading and Unmasking the Data
If the MASK bit was set (and it should be, for client-to-server messages), read the next 4 octets (32 bits); this is the masking key. Once the payload length and masking key is decoded, you can go ahead and read that number of bytes from the socket. Let's call the data ENCODED, and the key MASK. To get DECODED, loop through the octets (bytes a.k.a. characters for text data) of ENCODED and XOR the octet with the (i modulo 4)th octet of MASK. In pseudo-code (that happens to be valid JavaScript):
var DECODED = "";
for (var i = 0; i < ENCODED.length; i++) {
DECODED[i] = ENCODED[i] ^ MASK[i % 4];
}Now you can figure out what DECODED means depending on your application.
第二步:向客户端发送数据【封包】
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
5. 基于Python实现简单示例
a. 基于Python socket实现的WebSocket服务端:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import base64
import hashlib 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 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 def run():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8003))
sock.listen(5) conn, address = sock.accept()
data = conn.recv(1024)
headers = get_headers(data)
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" value = headers['Sec-WebSocket-Key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
conn.send(bytes(response_str, encoding='utf-8')) 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() if __name__ == '__main__':
run()
b. 利用JavaScript类库实现客户端
<!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>
6. 基于Tornado框架实现Web聊天室
Tornado是一个支持WebSocket的优秀框架,其内部原理正如1~5步骤描述,当然Tornado内部封装功能更加完整。
以下是基于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()
app.py
<!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>
index.html
参考文献:https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
wepsocket 了解一下的更多相关文章
随机推荐
- 23.NULL 函数
SQL ISNULL().NVL().IFNULL() 和 COALESCE() 函数 请看下面的 "Products" 表: P_Id ProductName UnitPrice ...
- 第02章-装配Bean
1. Spring配置的可选方案 在XML中进行显式配置: 在Java中进行显式配置: 隐式的bean发现机制和自动装配. 2. 自动化装配bean Spring从两个角度来实现自动化装配: 组件扫描 ...
- 编写高质量代码改善C#程序的157个建议——建议8: 避免给枚举类型的元素提供显式的值
建议8: 避免给枚举类型的元素提供显式的值 一般情况下,没有必要给枚举类型的元素提供显式的值.创建枚举的理由之一,就是为了代替使用实际的数值.不正确地为枚举类型的元素设定显式的值,会带来意想不到的错误 ...
- 【C#】浅克隆和深克隆的区别和在C#中的体现形式
前言:我们知道对象分引用类型和值类型. 浅克隆:复制一个现有对象,引用类型指向同一个内存块(string为最特殊的对象,这里当作值类型来看先) public class User { public i ...
- arcconf工具相关命令V1.0
arcconf工具相关命令V1.0 清除当前所有raid配置 Arcconf delete 1 array all #删除所有逻辑盘 Arcconf uninit 1 all ...
- IE11下使用fixed定位时鼠标滚动不平滑
很久不用IE了,近期做兼容性测试发现一个fixed定位的问题,当元素使用fixed定位时,其应该不随页面滚动,在chrome/firefox/edge下都很完美,元素完全不动,但是使用IE11时,如果 ...
- JMeter的使用——ApacheJMeterTemporaryRootCA.crt的用法
在使用JMeter的时候,启动HTTP代理服务器弹出的那个提示框一直不知道是什么意思,刚刚弄明白了,在JMeter2.1之后,通过JMeter的代理服务器来访问https安全连接的网页的时候,浏览器会 ...
- 关于MultiDataTrigger和MultiTrigger的一些注意事项
他俩有着相同的语法. 都是在conditions中编写触发条件. 因为都是同一个触发类. 在conditions中有Property和Binding这两个属性.那么这两个可以同时使用吗?当然是不可以的 ...
- python merge、concat合并数据集
数据规整化:合并.清理.过滤 pandas和python标准库提供了一整套高级.灵活的.高效的核心函数和算法将数据规整化为你想要的形式! 本篇博客主要介绍: 合并数据集:.merge()..conca ...
- kali linux之webshell
webacoo(web backdoor cookie) 类终端的shell 编码通信内容通过cookie头传输,隐蔽性较强 cm:base64编码的命令 cn:服务器用于返回数据的cookie头的名 ...