即时通讯简称IM(Instant Messaging)
需求:用户不只是在消息通知页面可以看到消息,而是在所有页面都能马上得到关注的消息
思考:http只有请求才有响应,但是上面的需求需要服务器主动把关注的消息推送给你,http无法满足
IM的类型:在线推送和离线推送(区别在于应用是否打开)
IM如何构建:我们可以自己搭建WebSocket来推送,或者使用第三方IM服务商提供的服务
IM服务商:
+ 网易云信
+ 融云
+ 环信
+ LeanCloud
即使通讯运用的场景:美团外卖又有新订单了
思考:Http究竟能不能完成即时通讯呢?
图解:HTTP1.0 在每次请求完服务器后,都会断开tcp连接。默认的keepalive是不开启的,无法保持连接
HTTP1.1默认keepalive是开启的
需求:客户端如何通过Http获得服务器数据呢?
轮询:5s发送一次请求,看看有木有新数据(服务器压力比较大)
comet长连接:客户端发起请求,如果服务器没有新数据则不响应,
等到有服务器有新数据响应返回后,客户端立即发起新的请求
缺点:客户端主动请求,不是服务器推送的
新的解决方案(服务器主动推送):
+ WebSocket(由 HTML5定义)
+ HTTP/2
WebSocket
WebSocket定义:是建立在单次TCP连接的全双工通信协议
细节:HTTP是一锤子买卖,WebSocket是建立起长连接,可以互相发送消息
通过上面两图的对比可以知道,WebSocket建立的TCP连接可以发送多次非HTTP协议的数据,这样就能实现服务器主动推送消息了。进行tcp三次握手之后的第一次发送ws握手数据,也是一次握手,正是因为这次握手,两者才建立了持久的连接,并进行双向数据传输。
典型的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在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。
- 没有同源限制,客户端可以与任意服务器通信。
- 可以发送文本,也可以发送二进制数据。
Socket.IO
细节:Socket .IO是WebSocket封装之后的工具,我们不使用原生的WebSocket进行开发
细节:古老浏览器不支持WebSocket,那么就降级为轮询的方式,所以我们要实现两种方案(哭泣)
缺点:和原生的WebSocket数据传输稍微不一样,所以客户端和服务器端都要使用Socket.IO
安装:pipinstall python-socketio
创建服务器:
方式1:单独部署(如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)
方式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服务器时的工作模式是用多进程或者多线程模式运行应用的,但是如果是这样,客户端每次建立一个请求创建一个线程,这个线程因为长连接一直存在,不能被销毁,这样如果客户端请求特别多,那么线程也特别多,还不能销毁,这样会导致服务器效率低下的。
方式三:协程是方式一的解决方案。
协程
进程-线程-协程的关系:
- 进程和进程之间是隔离的。每个进程各自拥有自己的内存。
- 线程不同之处在于计数器(表示代码执行的位置)
- 进程是操作系统分配资源的最小的单位,线程是操作系统调度(调度就是看看计数器到那里了,那就从那里开始执行代码)的最小单位
- 比喻:进程就是公司,线程就是在公司下做事的我们(有的负责提需求,有的负责开发,有的负责测试),协程就是我手动上有三件事情(函数)(吃饭-看文章-喝水),我吃一口饭,然后停下来看一下文章,在停下来喝一口水,这样反复操作,完成吃饭-看文章-喝水三件事情,速度太快了宏观上感觉是并行处理的。
- 协程调度是由程序员调度了,而不是操作系统,因为这个粒度太小了管不到
- yield 暂停代码,保存/恢复现场(就是恢复程序运行所需的变量)
- gevent和eventlet库是专门操作协程的库
切换的时机:
+ 在程序中发生阻塞的时候(读磁盘,写文件,网络IO操作,收发HTTP请求)
协程库原理:
-
封装了阻塞的代码,比如原生的read() 被重新封装为:
def read(): 进行操作系统调用read()非阻塞的 yield 暂停代码·
-
eventlet.mokey_patch()会替换调全部原生的阻塞代码
处理事件
细节:Socket.IO并不是用请求和响应处理数据,而是将数据当作消息,消息根据类别分为事件,然后处理事件
细节:关注通知属于关注事件,评论文章通知属于评论事件,不同消息通知对应不同的事件处理
事件的类型:
+ connect
+ disconnect
+ own event
事件就是给消息打上的标签而已
@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
发送事件
-
群发
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)
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()
技巧:将多个模块需要使用的对象提取出来,作为模块导入。
细节:所以绑定的事件要先在sio服务器启动前import导入进去,这样服务器才知道它绑定的方法
插件:firecamp来测试sockio
关注业务:
- 首先在web服务器要将关注数据写入数据库中的表中
- 还要将数据写入消息队列,让IM服务器来推送关注用户给博主
细节:IM服务器会不断轮询消息队列去问有木有要推送的任务
关于博主
【如果文章有错误,或者需要学习资料还是想一起学习大数据或者人工智能的朋友可以加下面微信
在朋友圈不定期直播自己的一些学习心得和面试经历。(请备注CSDN)】