python之websocket
一、websocket
WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。
本文将使用Python编写Socket服务端,一步一步分析请求过程!!!
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实现解包过程(未实现长内容)
解包详细过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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 ... | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
第二步:向客户端发送数据【封包】
- 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
python之websocket的更多相关文章
- python实现websocket服务器,可以在web实时显示远程服务器日志
一.开始的话 使用python简单的实现websocket服务器,可以在浏览器上实时显示远程服务器的日志信息. 之前做了一个web版的发布系统,但没实现在线看日志,每次发布版本后,都需要登录到服务器上 ...
- python 实现websocket
python中websocket需要我们自己实现握手代码,流程是这样:服务端启动websocket服务,并监听.当客户端连接过来时,(需要我们自己实现)服务端就接收客户端的请求数据,拿到请求头,根据请 ...
- HTTPS请求HTTP接口被浏览器阻塞,python实现websocket客户端,websocket服务器,跨域问题,dwebsocket,https,拦截,服务端
HTTPS请求HTTP接口被浏览器阻塞,python实现websocket客户端,websocket服务器,跨域问题,dwebsocket,https,拦截,服务端 发表时间:2020-03-05 1 ...
- 利用python对websocket进行并发压测
简述 产品经理鉴于运营反馈并对程序的websocket长连接保持怀疑的态度,让我对websocket服务器进行压力测试,我内心是拒绝的. 开发思路 查阅websocket的相关资料,查到python的 ...
- Python基于websocket实时通信的实现—GoEasy
Python websocket实时消息推送 在这里我记录一下之前如何实现服务器端与客户端实时通信: 实现步骤如下: 1. 获取GoEasy appkey. 在goeasy官网上注册一个 ...
- python tornado websocket 多聊天室(返回消息给部分连接者)
python tornado 构建多个聊天室, 多个聊天室之间相互独立, 实现服务器端将消息返回给相应的部分客户端! chatHome.py // 服务器端, 渲染主页 --> 聊天室建立web ...
- 基于python的websocket开发,tomcat日志web页面实时打印监控案例
web socket 接收器:webSocket.py 相关依赖 # pip install bottle gevent gevent-websocket argparse from bottle i ...
- 用python实现websocket请求遇到的问题及解决方法。
想要实现python的ws库功能,实时获取对方服务器ws协议返回的数据,查了下百度,用如下流程: ws = create_connection("wss://ws.xxxxxxx.info/ ...
- python 版websocket实现
ubuntu下python2.76 windows python 2.79, chrome37 firefox35通过 代码是在别人(cddn有人提问)基础上改的, 主要改动了parsedata和se ...
- python模拟websocket握手过程中计算sec-websocket-accept
背景 以前,很多网站使用轮询实现推送技术.轮询是在特定的的时间间隔(比如1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给浏览器.轮询的缺点很明显,浏览器需要不断的向服 ...
随机推荐
- float元素一定要闭合
float:left; float:right; 一定要两个元素一起放float
- less-!important关键字
//!important关键字 使用!important关键字混入调用之后,以标记它继承的所有属性!important,example: .test{ background:red; font-siz ...
- mysql delete 多表
DELETE u.*,acu.*,alu.* FROM user u, accountuser acu ,algouser alu WHERE u.userId=11 and acu.userID=1 ...
- Learning PHP Design Patterns
Learning PHP Design Patterns CHAPTER 1 Algorithms handle speed of operations, and design patterns ha ...
- Storm-源码分析-Topology Submit-Executor
在worker中通过executor/mk-executor worker e, 创建每个executor (defn mk-executor [worker executor-id] (let [e ...
- django 表单系统 之 forms.Form
继承forms.Form实现django表单系统 参考: https://www.cnblogs.com/zongfa/p/7709639.html https://www.cnblogs.com/c ...
- Hibernate缓存原理
对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键. 简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等, 但随之带来的就是数据访问 ...
- Django 之基础学习
阅读目录 配置 视图层之路由系统配置 模版层 模版过滤器 request & response Ajax Cookie Session 分页 文件传输 Django MTV模型 Django ...
- Django中间件(含Django运行周期流程图)
Django中的中间件(含Django完整生命周期图) Django中间件简介 django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,djang ...
- Delphi 正则表达式语法(6): 贪婪匹配与非贪婪匹配
Delphi 正则表达式语法(6): 贪婪匹配与非贪婪匹配 //贪婪匹配 var reg: TPerlRegEx; begin reg := TPerlRegEx.Create(nil); ...