websocket理解
简介
在实际开发中,可能会出现一个需求场景,要求网页的数据可以实时更新。在这种情况下,我们一般会采用轮询的方式,间隔性获取数据,即通过定时器间隔性请求相应接口获取数据,此方式由于是不断请求服务器,资源开销相对较大,且由于数据更新是间隔性,会导致数据时效性欠缺,可能会出现部分延迟,因此衍生出另一种方式:长轮询,长轮询一般是客户端请求服务端,但服务器不是即时返回,而当内容有所更新时,服务器会返回相应内容给客户端,从名义上为服务器向客户端推送信息。
综合以上,可以总结出轮询、长轮询的优缺点
轮询:
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_sock、flask_sock文档
websocket理解的更多相关文章
- PHP - Swoole websocket理解
php swoole实现websocket功能 1.确保安装了swoole扩展. 2.撰写服务程序 <?php //创建websocket服务器对象,监听0.0.0.0:9502端口 $ws = ...
- 从配置websocket理解nginx
原文地址:http://www.niu12.com/article/2 今天由于写了一个简单的基于h5 websoceket的聊天室,再本地都是好好了. 但是上到服务器后就发现无法行的通, 查 ...
- websocket(二) websocket的简单实现,识别用户属性的群聊
没什么好说的,websocket实现非常简单,我们直接看代码. 运行环境:jdk8 tomcat8 无须其他jar包. 具体环境支持自己百度 package com.reach.socketContr ...
- 简版在线聊天Websocket
序言 What is Webscoket ? websocket 应用场景 简版群聊实现 代码例子 小结 Webscoket Websokcet 是一种单个TCP连接上进行全双工通信的协议,通过HTT ...
- Swoole http server + yaf, swoole socket server + protobuf 等小结
拥抱swoole, 拥抱更好的php Swoole 是什么? Yaf 是什么? 接触swoole已经4年多了,一直没有好好静下心来学习.一直在做web端的应用,对网络协议和常驻内存型服务器一窍不通.一 ...
- 理解WebSocket
WebSocket的动机是什么? 目前的Web通信使用的是HTTP协议,HTTP协议是基于TCP协议的应用层协议,HTTP协议的工作模式是request/response模式.在一次通信中,必须首先由 ...
- websocket教程(一) 非常有趣的理解websocket
一.websocket与http WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算) 首先HTTP有 1 ...
- 我理解的websocket
短轮询:客户端发起请求,服务器无论有无消息都返回信息,结束http连接.然后继续发起请求. 长轮询:客户端发起请求,建立连接,直到服务端返回消息response,结束http连接.然后继续发起请求,重 ...
- 理解WebSocket心跳及重连机制(五)
理解WebSocket心跳及重连机制 在使用websocket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件.这样会有:服务器会继续向客户端发送多余的 ...
随机推荐
- Python模块 | EasyGui
(Python模块 | EasyGui | 2021/04/08) 目录 什么是 EasyGUI? [EasyGui中的函数] msbox | 使用示例 ynbox | 使用示例 ccbox | 使用 ...
- SpringBoot 2.X 快速掌握
0.重写博文的原因 当初我的SpringBoot系列的知识是采用分节来写的,即:每一个知识点为一篇博文,但是:最近我霉到家了,我发现有些博文神奇般地打不开了,害我去找当初的markdown笔记,但是方 ...
- springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑)
springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑) 写在前面: 富文本编辑器,Multi-function Text Editor, 简称 MTE, 是一 ...
- Redis初启(一)
1.数据库存存储性能优化 在mysql的文章专题中我写过了关于传统关系型数据库的一些优化思路,整体来说,通过优化之后能够提升程序访问数据库的计算性能.但是还是有一些情况,即便是优化之后,使用传统关系型 ...
- dubbo的消费者是怎么获取提供者服务接口引用的?
本文主要解读dubbo消费者是如何引用服务端接口的,是如何像本地调用一样调用远程服务的. 并试着从设计者的角度思考,为何这样设计. @Component public class DubboConsu ...
- 《Java笔记——基础语法》
Java笔记--基础语法 一.字符串的拼接: 例如: System.out.println(""+""); 二.换行语句: 例如: Syst ...
- Windows-安装OpenVINO
安装指导书链接: https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_wi ...
- 安装pystaller
安装命令 # -i指定下载地址,此处采用清华大学镜像 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package pyin ...
- C++ 练气期之一文看懂字符串
C++ 练气期之细聊字符串 1. 概念 程序不仅仅用于数字计算,现代企业级项目中更多流转着充满了烟火气的人间话语.这些话语,在计算机语言称为字符串. 从字面上理解字符串,类似于用一根竹签串起了很多字符 ...
- docker-compose: 未找到命令,安装docker-compose
1.安装扩展源 sudo yum -y install epel-release 2.安装python-pip模块 sudo yum install python-pip 3.通过命令进行安装 cd ...