简介

在实际开发中,可能会出现一个需求场景,要求网页的数据可以实时更新。在这种情况下,我们一般会采用轮询的方式,间隔性获取数据,即通过定时器间隔性请求相应接口获取数据,此方式由于是不断请求服务器,资源开销相对较大,且由于数据更新是间隔性,会导致数据时效性欠缺,可能会出现部分延迟,因此衍生出另一种方式:长轮询,长轮询一般是客户端请求服务端,但服务器不是即时返回,而当内容有所更新时,服务器会返回相应内容给客户端,从名义上为服务器向客户端推送信息。

综合以上,可以总结出轮询、长轮询的优缺点

轮询:

1.服务器的CPU、带宽资源消耗较大,也可能会存在大量的无效请求
2.由于是间隔性获取数据,数据时效性欠缺,会有部分延迟,当然,间隔时间越短,相对的数据的时效性越好,但会导致资源开销大

长轮询

1.解决了轮询过程中的数据时效性较差的问题
2.由于一直在等待服务器返回,对于内存的消耗会相对较大

轮询、长轮询都或多或少存在一些缺陷,因此websocket协议就此诞生,它就是为了实现消息的实时更新而出现的。

websocket简介

websocket协议是网络传输协议,位于应用层,可在单个tcp连接上进行全双工通信,能够更好的节省资源开销实现消息通讯,websocket基于tcp协议,所以也是需要建立连接和关闭连接的,不像tcp的三次握手,客户端与服务器建立一次连接后即可实现双向通信。

为了实现和HTTP的兼容性,WebSocket握手使用HTTP的Upgrade头将HTTP协议转换成Websocket协议。

作为一种协议,websocket自然也是有其用于协议控制的头部信息的,但是相对于HTTP请求每次都要带上完整的头部信息,传输数据时,websocket数据包的头部信息就相对较小,从而降低了控制开销。

特点

  • 全双工通信;

    在数据传输中,有三种类型;单工通信、半双工通信、全双工通信,
单工通信:只能向一个方向进行传输
半双工通信:可以双向传输,但是不能同时进行
全双工通信:可以双向传输,也可以同时传输
  • 二进制帧

    采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,相比http/2,WebSocket更侧重于“实时通信”,而HTTP/2 更侧重于提高传输效率
  • 协议名与http协议类似

    引入ws和wss分别代表明文和密文的websocket协议,且默认端口使用80或443,与http几乎一致

websocket建立连接的流程

1.建立连接, 客户端与服务器端连接
2.握手:验证服务器是否支持websocket
发送的数据如下:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com Sec-WebSocket-Key:浏览器随机生成的key,base64生成
Sec-WebSocket-Protocol:websocket的协议
Sec-WebSocket-Version:websocket的版本
其中客户端有一个:Sec-WebSocket-Key字段,服务器有一个:Sec-WebSocket-Accept字段,
浏览器发送随机生成的key到服务器,
服务器返回对应Sec-WebSocket-Accept key的原理:
取出Sec-WebSocket-Key(随机产生),与一个magic string “258EAFA5-E914-47DA-95CA-C5AB0DC85B11” 连接成一个新的key串
将新的key串SHA1编码,生成一个由多组两位16进制数构成的加密串
把加密串进行base64编码生成最终的key,这个key就是Sec-WebSocket-Accept
简化:取出浏览器发送的key 与magic string连接成一个新字符串
对新key进行sha1加密返回新的加密串
对新的加密串进行base64加密并将结果返回给浏览器
浏览器接收到key与自己本地加密的key进行比较,如果验证通过则说明支持websocket协议,则进行下一步收发数据动作
为了表示服务器同意和客户端进行Socket连接, 服务器端需要使用客户端发送的这个Key进行校验 ,然后返回一个校验过的字符串给客户端,客户端验证通过后才能正式建立Socket连接。
3.收发数据
4.关闭连接

简易流程如下:

1.建立连接
2.握手:校验服务器是否支持websocket协议:
3.收发数据
4.关闭连接(一般为长连接)

websocket的优点

  • 较少的控制开销:数据包头部协议较小,不同于http每次请求需要携带完整的头部
  • 更强的实时性:相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少
  • 保持连接状态:创建通信后,可省略状态信息,不同于HTTP每次请求需要携带身份验证
  • 更好的二进制支持:定义了二进制帧,更好处理二进制内容

    支持扩展:用户可以扩展websocket协议、实现部分自定义的子协议
  • 更好的压缩效果:Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率

websocket的异常处理

由于websocket可能由于网络波动、连接异常断开导致无法正常进行通信,因此对于一般websocket而言,需要添加异常处理机制,即在出现异常断开时进行重连,因此衍生出心跳重连机制。

心跳重连机制:
原因:由于网络原因导致的websocket连接未能正常关闭,从而导致服务器继续推送数据导致数据丢失,因此需要一种判断客户端和服务端是否存货的方式
概念: 心跳机制是每隔一段时间会向服务器发送一个数据包,告诉服务器自己还活着,同时客户端会确认服务器端是否还活着,如果还活着的话,就会回传一个数据包给客户端来确定服务器端也还活着,否则的话,有可能是网络断开连接了。需要重连~

需要注意的是,在websocket的一方出现异常时,需要先将原始连接关闭后再建立新的连接,如果原始连接还在,可能会出现消息数据发送错误的情况

websocket的使用场景

  • 视频平台的弹幕发送
  • 聊天室
  • 协同编辑:例如在线文档的多人协作
  • 服务器推送客户端消息
  • 其他需要保持长时间实时更新的任务或需求

websocket在python中的简单使用

客户端

首先先创建一个简易的服务端,使用flask_sockets创建

from flask import Flask
from flask_sockets import Sockets app = Flask(__name__)
socket = Sockets(app=app) @socket.route('/start_websocket')
def start_websocket(ws):
try:
while not ws.closed:
data = ws.receive()
print(f'recv data:{data}')
ws.send(data)
except Exception as e:
print(f'start_websocket error:{e}') if __name__ == "__main__":
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
server = pywsgi.WSGIServer(listener=("0.0.0.0", 5050), application=app, handler_class=WebSocketHandler)
print('server start')
server.serve_forever()
  • create_connection连接

    此方法不建议使用,链接不稳定,容易断,并且连接很耗时
pip3 install websocket-client
from websocket import create_connection

url='ws://127.0.0.1:5050/start_websocket'
ws = create_connection(url)
ws.send('hello')
while True:
recv_data = ws.recv()
print(f'recv_data:{recv_data}')
  • ws4py.client.threadedclient import WebSocketClient

    此方法很容易连接,获取数据的速度也挺快
# 需要安装ws4py
pip3 install ws4py
from ws4py.client.threadedclient import WebSocketClient

class EchoClient(WebSocketClient):
def opened(self):
self.send('hello') def closed(self, code, reason):
print(("Closed down", code, reason)) def received_message(self, m):
print("=> %d %s" % (len(m), str(m))) try:
ws = EchoClient('ws://127.0.0.1:5050/start_websocket', protocols=['http-only', 'chat'])
ws.connect()
ws.run_forever()
except KeyboardInterrupt:
ws.close()
  • 使用websocket自带的Websocket

    与第一种方式类似,容易断,连接耗时长
pip install websocket
from websocket import WebSocket

try:
ws = WebSocket()
url='ws://127.0.0.1:5050/start_websocket'
ws.connect(url)
ws.send('hello')
while True:
data = ws.recv()
print(f'data:{data}')
except Exception as e:
print(f'start websocket fail:{e}')
  • 使用websocket模块的WebSocketApp

    使用方法类似于js的websocket api
# 需要安装websocket
pip3 install websocket
from websocket import WebSocketApp
import websocket def open(ws:WebSocketApp):
print(f'ws:{ws}')
ws.send('hello') def receive_msg(ws, msg):
print(f'ws:{ws}, msg:{msg}') def error(ws, error):
print(f'ws:{ws}, error:{error}') def close(ws, status, code):
print(f'ws:{ws}, status:{status}, code:{code}') if __name__ == "__main__":
# 打开可追溯性,打印项目日志debug
websocket.enableTrace(True)
url='ws://127.0.0.1:5050/start_websocket'
ws = WebSocketApp(url=url, on_open=open, on_message=receive_msg, on_error=error, on_close=close)
ws.run_forever()

结果如下

--- request header ---
GET /start_websocket HTTP/1.1
Upgrade: websocket
Host: 127.0.0.1:5050
Origin: http://127.0.0.1:5050
Sec-WebSocket-Key: xP5FfO+M3UzHuug2rjAMMA==
Sec-WebSocket-Version: 13
Connection: Upgrade -----------------------
--- response header ---
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: eKkIqEQa+7niXqEbW6bHN1tMnA0=
-----------------------
ws:<websocket._app.WebSocketApp object at 0x7f8d8721acc0>
++Sent raw: b'\x81\x85\xa7\xc1\xf2\x11\xcf\xa4\x9e}\xc8'
++Sent decoded: fin=1 opcode=1 data=b'hello'
++Rcv raw: b'\x81\x05hello'
++Rcv decoded: fin=1 opcode=1 data=b'hello'
ws:<websocket._app.WebSocketApp object at 0x7f8d8721acc0>, msg:hello

由此可以看出,发送的key为eKkIqEQa+7niXqEbW6bHN1tMnA0=,协议为websocket,版本为13

服务端

  • flask_sockets

    上文已经写了一个简单的服务端程序
from flask import Flask
from flask_sockets import Sockets app = Flask(__name__)
socket = Sockets(app=app) @socket.route('/start_websocket')
def start_websocket(ws):
try:
while not ws.closed:
data = ws.receive()
print(f'recv data:{data}')
ws.send(data)
except Exception as e:
print(f'start_websocket error:{e}') if __name__ == "__main__":
from gevent import pywsgi
# 将连接转为websocket协议,具体连接过程详见上述连接过程的建立连接
from geventwebsocket.handler import WebSocketHandler
server = pywsgi.WSGIServer(listener=("0.0.0.0", 5050), application=app, handler_class=WebSocketHandler)
print('server start')
server.serve_forever()

WebSocketHandler:就是将连接转为websocket协议,存在一个magic string,以及支持的版本

SUPPORTED_VERSIONS = ('13', '8', '7')
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

注意事项

flask和Flask-Sockets版本不能过高, 过高会导致无法正常启动websocket
flask:1.1.2
Flask-Sockets:1.0.1
Werkzeug:1.0.0
另外,此方法在官方文档中已经不再推荐

参考链接 flask_websockts

  • flask-sockeio
pip3 install flask-socketio

必要条件:

Flask SocketIO与Python 3.6+兼容。此包所依赖的异步服务可以在三个选项中选择:

1.eventlet

2.gevent

3.Werkzeug

from flask import Flask
# send:用于自带的event发送数据, emit用于定制的event发送数据
from flask_socketio import SocketIO,send, emit app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
socketio = SocketIO(app) '''
使用socketio.on装饰器来接收从客户端发送来的WebSocket信息。
socketio.on的第一个参数是event名称。connect, disconnect, message和json是SocketIO产生的特殊events。
其它event名称被认为是定制events。
'''
@socketio.on('message', namespace='/start_websocket')
def handle_message(message):
print(f'handle_message:{message}')
send(message) @socketio.on('json')
def handle_json(json):
print('received json: '+ str(json)) @socketio.on('my event')
def handle_my_custom_event(json):
print('received json: ' + str(json)) # 声明命名空间,方便多路复用, 类似于flask_websockets下的socket.route中的参数
@socketio.on('my event2', namespace='/test')
def handle_my_custom_namespace_event(json):
print('received json: ' + str(json)) if __name__ == "__main__":
# 函数socketio.run()封装了网络服务器的启动部分,并且代替了flask开发服务器的标准启动语句app.run()
socketio.run(app)

与其他服务端不同,客户端还需引用Socket.IO库并且建立一个连接:

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
var socket = io();
socket.on('connect', function() {
socket.emit('my event', {data: 'I\'m connected!'});
});
</script>

目前发现比较适用与前端界面到后端的交互

参考文档:flask_socketio官网flask_socketio中文翻译

  • flask-sock

    此方法不再依赖于其他的异步组件
pip3 install flask-sock
from flask import Flask
from flask_sock import Sock app = Flask(__name__)
sock = Sock(app)
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 25} @sock.route('/start_websocket')
def start_websocket(ws):
while True:
data = ws.receive()
print(f'data:{data}')
if data != 'close':
ws.send(data)
else:
ws.close()
if __name__ == "__main__":
app.run(port=5050)

flask-sock的适用方式与flask_sockets类似,但是,它不依赖于gevent等,类似于http请求接口一样,并且自带发送ping消息的功能。

参考文档:flask_sockflask_sock文档

最新文章

  1. 如何判断自己的VPS是那种虚拟技术实现的
  2. Netty In Action
  3. note of introduction of Algorithms(Lecture 3 - Part1)
  4. Mac shell 添加VPN 路由
  5. (转)UML常用图的几种关系的总结
  6. Android大图片裁剪终极解决方案 原理分析
  7. 64位windows7 上安装32位oracle 10g 的方法
  8. CSS中.和#区别
  9. 小测试 php代理,nginx代理,直接访问对比
  10. CodeForces 712D Memory and Scores
  11. [java面试]逻辑推理6 10 18 32 下一个数?编程实现输入任意一个N位置,该数是多少?java实现
  12. 基于MongoDB.Driver的扩展
  13. ReSharper 10.0.0.2 Ultimate 破解
  14. Hive中变量的使用
  15. [LeetCode&amp;Python] Problem 268. Missing Number
  16. 理解 async/await 的执行
  17. Eclipse调试Java的10个技巧【转】
  18. Java虚拟机八 分析Java堆
  19. 前端基础(JavaScript)2
  20. python爬虫同时输出两个列表(zip函数)

热门文章

  1. 防火墙之ipset表应用
  2. 命令行参数 getopt模块
  3. 被迫开始学习Typescript —— class
  4. Blazor和Vue对比学习(基础1.7):传递UI片断,slot和RenderFragment
  5. 详细剖析pyecharts大屏的Page函数配置文件:chart_config.json
  6. 文件操作(Java)
  7. python闭包函数与装饰器
  8. 详解SQL操作的窗口函数
  9. SAM[详细~bushi]
  10. uniapp项目vue2升级vue3简单记录