flask-web—— 即时通讯 IMWebSocketSocket.IO 实时推送消息
Posted 胖虎是只mao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flask-web—— 即时通讯 IMWebSocketSocket.IO 实时推送消息相关的知识,希望对你有一定的参考价值。
一、即时通讯简介
即时通讯(Instant Messaging)是一种基于互联网的即时交流消息的业务。
类型:
-
在线push
- 适用:web页面 和 App
- 自己构建IM服务器
- 使用WebSocket
- 采用成熟的框架方案Socket.IO
- 对于App还可自己封装socket
- 使用第三方IM服务商提供的服务
-
离线push
- 适用:App
- 对于ios,使用APNs
- 对于andorid,使用FCM(国外)或第三方IM服务商提供的服务
提供第三方IM服务的服务商有:
网易云信
融云
环信
LeanCloud
下面只针对 「在线推送」 的自建方案来展开讲解。
二、需求场景
服务端需要主动推送消息给客户端,例如
- 用户下了订单,需要在运营管理后台向运营人员推送新订单通知
- 用户A关注了用户B,系统需要向用户B推送提示消息
- 即时聊天
传统的推送实现
HTTP/1.x 不支持服务器主动推送,只能在客户端发起请求后做出回应。
因为HTTP/1.0
需要自己手动定制 keepalive的请求头,表示保持连接,在收到响应之后不会断开 。而 HTTP/1.1
默认开启了keepalive,使用长连接。 但是keepalived只是声明,表明想要使用长连接,但是通讯双方都有权随时关闭连接
keepalive是保证双方发起多个请求和响应的时候,使用同一个连接
(HTTP/2支持服务器主动推送,但HTTP/2 还未全面实施)
轮询
轮询是在特定的的时间间隔(如每1秒),由客户端对服务器发出HTTP请求,了解服务器有没有新的信息,然后由服务器告知有无新数据或返回最新的数据给客户端。
缺点:
效率低下,浪费资源
必须不停连接,或者连接始终打开,但传输HTTP请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
Comet (基于长连接)
长轮询 长轮询是在打开一条连接以后保持,等待服务器推送来数据再关闭的方式。
iframe流 iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长链接,服务器向iframe传输数据(通常是html,内有负责插入信息的javascript),来实时更新页面。
缺点:
依然需要反复发出请求,而且长连接也会消耗服务器资源。
三、WebSocket
HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
在2008年诞生,2011年成为国际标准。
现在基本所有浏览器都已经支持了。
WebSocket是一种在单个TCP连接上进行全双工通信的协议。在WebSocket API中,浏览器和服务器只需要完成一次握手(不是指建立TCP连接的那个三次握手,是指在建立TCP连接后传输一次握手数据),两者之间就直接可以创建持久性的连接,并进行双向数据传输。
Websocket使用ws或wss的统一资源标志符,类似于HTTPS,其中wss表示在TLS之上的Websocket。如:
ws://example.com/wsapi
wss://secure.example.com/
Websocket使用和 HTTP 相同的 TCP 端口,可以绕过大多数防火墙的限制。默认情况下,Websocket协议使用80端口;运行在TLS之上时,默认使用443端口。
握手协议
WebSocket 是独立的、创建在 TCP 上的协议。 报文
Websocket 通过 HTTP/1.1 协议的101状态码进行握手。
为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。
一个典型的Websocket握手请求如下:
客户端请求
第一次握手:
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
服务器回应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
- Connection必须设置Upgrade,表示客户端希望连接升级。
- Upgrade字段必须设置Websocket,表示希望升级到Websocket协议。
- Sec-WebSocket-Key是随机的字符串,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一个特殊字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算SHA-1摘要,之后进行BASE-64编码,将结果做为“Sec-WebSocket-Accept”头的值,返回给客户端。如此操作,可以尽量避免普通HTTP请求被误认为Websocket协议。
- Sec-WebSocket-Version 表示支持的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均应当弃用。
- Origin字段是可选的,通常用来表示在浏览器中发起此Websocket连接所在的页面,类似于Referer。但是,与Referer不同的是,Origin只包含了协议和主机名称。
- 其他一些定义在HTTP协议中的字段,如Cookie等,也可以在Websocket中使用
优点
- 较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。
- 更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。
- 保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。 更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。
- 可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
- 更好的压缩效果。相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。
- 没有同源限制,客户端可以与任意服务器通信。
- 可以发送文本,也可以发送二进制数据。
HTTP 和TCP之间的关系:
WebSocket和TCP之间的关系:
四、Socket.IO
1 简介
Socket.IO
本是一个面向实时 web 应用的 JavaScript 库,现在已成为拥有众多语言支持的Web即时通讯应用的框架。
Socket.IO 主要使用WebSocket协议。但是如果需要的话,Socket.io可以回退到几种其它方法,例如Adobe Flash Sockets,JSONP拉取,或是传统的AJAX拉取,并且在同时提供完全相同的接口。尽管它可以被用作WebSocket的包装库,它还是提供了许多其它功能,比如广播至多个套接字,存储与不同客户有关的数据,和异步IO操作。
Socket.IO 不等价于 WebSocket,WebSocket只是Socket.IO实现即时通讯的其中一种技术依赖,而且Socket.IO还在实现WebSocket协议时做了一些调整。
优点:
Socket.IO 会自动选择合适双向通信协议,仅仅需要程序员对套接字的概念有所了解。
有Python库的实现,可以在Python实现的Web应用中去实现IM后台服务。
缺点:
Socket.io并不是一个基本的、独立的、能够回退到其它实时协议的WebSocket库,它实际上是一个依赖于其它实时传输协议的自定义实时传输协议的实现。该协议的协商部分使得支持标准WebSocket的客户端不能直接连接到Socket.io服务器,并且支持Socket.io的客户端也不能与非Socket.io框架的WebSocket或Comet服务器通信。因而,Socket.io要求客户端与服务器端均须使用该框架。
2. Python服务器端开发
文档https://python-socketio.readthedocs.io/en/latest/server.html
安装
pip install python-socketio
创建服务器
-
方式1
使用多进程多线程模式的WSGI服务器对接(如uWSGI、gunicorn)
import socketio # create a Socket.IO servers sio = socketio.Server() # 打包成WSGI应用,可以使用WSGI服务器托管运行 app = socketio.WSGIApp(sio) # Flask Django
创建好app对象后,使用uWSGI、或gunicorn服务器运行此对象。
-
方式2
作为Flask、Django 应用中的一部分
from wsgi import app # a Flask, Django, etc. application import socketio # create a Socket.IO server sio = socketio.Server() app = socketio.WSGIApp(sio, app)
创建好app对象后,使用uWSGI、或gunicorn服务器运行此对象。
-
方式3
使用协程的方式运行 (推荐)
import eventlet eventlet.monkey_patch() import socketio import eventlet.wsgi sio = socketio.Server(async_mode='eventlet') # 指明在evenlet模式下 app = socketio.Middleware(sio) eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
符合WSGI协议的服务器 工作模式时多进程加多线程
说明
因为服务器与客户端进行即时通信时,会尽可能的使用长连接,所以若服务器采用多进程或多线程方式运行,受限于服务器能创建的进程或线程数,能够支持的并发连接客户端不会很高,也就是服务器性能有限。采用协程方式运行服务器,可以提升即时通信服务器的性能。而且,如果即使用到子进程或者子线程,只要连接不断开,就永远也不会释放,所以不可能做到高并发。
事件处理
不同于HTTP服务的编写方式,SocketIO
服务编写不再以请求Request和响应Response来处理,而是对收发的数据以消息(message
)来对待,收发的不同类别的消息数据又以事件(event)来区分。
原本HTTP服务编写中处理请求、构造响应的视图处理函数在SocketIO服务中改为编写收发不同事件的事件处理函数。
1)事件处理方法
编写事件处理方法,可以接收指定的事件消息数据,并在处理方法中对消息数据进行处理。
@sio.on('connect')
def on_connect(sid, environ):
"""
与客户端建立好连接后被执行
:param sid: string sid是socketio为当前连接客户端生成的识别id
:param environ: dict 在连接握手时客户端发送的握手数据(HTTP报文解析之后的字典)
"""
pass
@sio.on('disconnect')
def on_disconnect(sid):
"""
与客户端断开连接后被执行
:param sid: string sid是断开连接的客户端id
"""
pass
# 以字符串的形式表示一个自定义事件,事件的定义由前后端约定
@sio.on('my custom event')
def my_custom_event(sid, data):
"""
自定义事件消息的处理方法
:param sid: string sid是发送此事件消息的客户端id
:param data: data是客户端发送的消息数据
"""
pass
注意
-
connect 为特殊事件,当客户端连接后自动执行
-
disconnect 为特殊事件,当客户端断开连接后自动执行
-
connect、disconnect与自定义事件处理方法的函数传入参数不同
2)发送事件消息
-
群发
sio.emit('my event', {'data': 'foobar'})
-
给指定用户发送
sio.emit('my event', {'data': 'foobar'}, room=user_sid)
-
给一组用户发送
SocketIO
提供了房间(room
)来为客户端分组-
sio.enter_room(sid, room_name)
将连接的客户端添加到一个room
@sio.on('chat') def begin_chat(sid): sio.enter_room(sid, 'chat_users')
注意:当客户端连接后,socketio会自动将客户端添加到以此客户端sid为名的room中
-
sio.leave_room(sid, room_name)
将客户端从一个room中移除
@sio.on('exit_chat') def exit_chat(sid): sio.leave_room(sid, 'chat_users')
-
sio.rooms(sid)
查询sid客户端所在的所有房间
给一组用户发送消息的示例
@sio.on('my message') def message(sid, data): sio.emit('my reply', data, room='chat_users')
也可在群组发消息时跳过指定客户端
@sio.on('my message') def message(sid, data): sio.emit('my reply', data, room='chat_users', skip_sid=sid)
-
使用send发送message事件消息
对于’message’事件,可以使用send方法
sio.send({'data': 'foobar'}) sio.send({'data': 'foobar'}, room=user_sid)
3、Python客户端
import socketio sio = socketio.Client() @sio.on('connect') def on_connect(): pass @sio.on('event') def on_event(data): pass sio.connect('http://10.211.55.7:8000') sio.wait()
-
进程、线程、协程区别:
五、协程
yield 生成器
def func():
print(1)
print(1)
print(1)
a = 100
b = 200
yield # 暂定代码 保存现场
print(1)
print(1)
generate_obj = func()
generate_obj.next()
generate_obj.next() # send()
切换的场景 在程序中发生阻塞的时候 (读磁盘 ,读写文件,网络IO操作,收发hTTP请求)
协程库
- gevent
- eventlet
协程库的原理
将可能发生阻塞操作的原生函数全部替换掉,会增加切换的指令,用猴子补丁进行替换 eventlet.monkey_patch()
import eventlet
eventlet.monkey_patch()
# 原生python方法
f = open('', 'rb')
f.read()
# 协程库提供改写的方法
def read():
进行操作系统调用 read #操作系统的非阻塞调用
yield
以上是关于flask-web—— 即时通讯 IMWebSocketSocket.IO 实时推送消息的主要内容,如果未能解决你的问题,请参考以下文章