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的事件.这样会有:服务器会继续向客户端发送多余的 ...
随机推荐
- 查重工具Jplag的使用
目录 前言 一.Jplag是什么? 二.使用步骤 1.下载包 2.java环境配置 3.如何使用 三.总结 前言 说明一下本文章针对最新版本Jplag3.0使用JplagAPI 一.Jplag是什么? ...
- 这 BUG,绝了
上周只上了三天班,但我也丝毫不敢懈怠,BUG 更是一个也没少写. 看着满屏幕的 ERROR,我陷入沉思.为什么我写的代如此烂,无法像大牛们写的那般优雅? 越想越自卑,越想越抑郁.我觉得这样不行,一定得 ...
- 面试官:ElasticSearch是什么,它有什么特性与使用场景?
哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,我本以为我跟面试我的 ...
- 前后端分离,SpringBoot如何实现验证码操作
验证码的功能是防止非法用户恶意去访问登录接口而设置的一个功能,今天我们就来看看在前后端分离的项目中,SpringBoot是如何提供服务的. SpringBoot版本 本文基于的Spring Boot的 ...
- linux篇-xshell连接突然报Connection closed by foreign host.
1问题描述报错 Connection closed by foreign host. Disconnected from remote host(yaoGS) at 155513. 2登入虚拟机 在l ...
- Hadoop: 单词计数(Word Count)的MapReduce实现
1.Map与Reduce过程 1.1 Map过程 首先,Hadoop会把输入数据划分成等长的输入分片(input split) 或分片发送到MapReduce.Hadoop为每个分片创建一个map任务 ...
- 理解RESTful Api设计
REST REST(REpresentational State Transfer)是 Roy Fielding 博士于 2000 年在他的博士论文中提出来的一种软件架构风格(一组架构约束条件和原则) ...
- 没错,就是Access-Control-Allow-Origin,跨域
服务端添加: <add name="Access-Control-Allow-Origin" value="*" /><add name=&q ...
- js算法-埃筛法
- 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...