背景
单纯的 websocket 通信方式存在大量的辅助性的工作需要处理,例如心跳机制、粘包处理、协议规范等,所以直接使用 websocket 开发,等于重复造轮子,毫无价值,而 socket.io 整理了一整套规范和机制,可以满足聊天室的功能,还能对不同的业务场景进行命名空间级别的隔离,简直不要太好用,完全满足我们这种低并发场景的各类需求且上手极其容易。
FastAPI 如何集成 socket.io?
有个 socket.io 的fastapi-socketio官方库,该库依赖传统的 python-socketio 库(这里请一定注意 socket.io-client.js 版本对应关系),fastapi-socketio 主要给出 fastapi 如何集成 python-socketio 的方法,省去了普通人将 python-socketio 集成到 fastapi 的工作。
代码 Demo
from fastapi import FastAPI
from fastapi_socketio import SocketManager
import uvicorn
app = FastAPI()
socket_manager = SocketManager(app=app, mount_location="/ws")
@socket_manager.on('connect',namespace="/ws")
async def connect(sid, environ):
print(sid)
print(environ)
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8888)
socket.io-client 的几个关键点
坑死你的命名空间
我们常规思维都是把 https://example.com/orders 这里的 orders 作为路径对待,可惜的是 socket.io 竟然把 host 之后的所有 path 都变成了 namespace,这是个大坑,一般人如果直接使用 host 作为 websocket 服务器一点问题都没有,但总有些情况下我们的 SSL 证书不足,我们没用二级域名,我们用了 path 来作为新的服务 endpoint,采用 nginx 进行反向代理过去,你以为会到 /orders,其实还是 https://example.com/socket.io?xxx... ,这让我百思不得其解,在多次确定服务端没有错误的情况下,我开始思考是不是 socket.io 不是这样配置的?
![](https://i-blog.csdnimg.cn/blog_migrate/4e31faf7f9e2c9893198bd31b38a3c18.png)
socket.io 命名空间用在哪里?
socket.io 原理详解 中我发现了engine.io,深入调试 socket.io 客户端源码时发现,socket.io 是建立在 engine.io 基础上针对 onmessage.data 结构设置了自己的一套协议,就叫 socket.io 协议,这个协议就有一个 namespace 的 key,这个 key 可以在 socket.io 协议层对事件进行区分,而不必新建一个 websocket,这???我还真没想到这种复用方式,也的确挺好,因此 /orders 被放到了 onmessage.data 中了,并没有在 connect 时,像 http 的方式发送给服务端了,问题来了/socket.io?xxx 这一坨参数是什么鬼?从哪里来的?
engine.io 中的 path
在 io(url, opts) 配置中,opts 有个 path 的 key,这个 key 默认就是 socket.io
![](https://i-blog.csdnimg.cn/blog_migrate/f3e9e84b8a13014f54e7c50e566a2e45.png)
虽然官方文档中也强调了,但谁能联想到?
![](https://i-blog.csdnimg.cn/blog_migrate/693cc0282561d0b0c5bccfa6f95ecf4d.png)
我们再来看下服务端的默认配置
![](https://i-blog.csdnimg.cn/blog_migrate/faf7f67c23f84eb3c1c676e38c4b3b38.png)
再来看看 fastapi-socketio 的源码
![](https://i-blog.csdnimg.cn/blog_migrate/ae78ec18245b93914455ae7e2bdf3105.png)
于是客户端的请求链接应该是这样的,是不是很惊讶?
socket = io('ws://localhost:8888/ws', {
transports: ['websocket'],
path:'/ws/socket.io'
});
socket.on('message', function(msg) {
console.log(msg);
});
socket.on('reply', function(msg) {
console.log(msg);
});
socket.io 技术选型?
因为 fastapi 主打的是异步框架,采用 asgi 的方式运行,所以python-socketio选择异步的方式进行
asgi 和 wsgi 两者的比较
以下文章讲得怪怪的,其实几句话就可以讲清楚,wsgi 是基于 python 同步语言时代产生的一种协议,按照这种协议开发出来 uwsgi 中间件可以同时满足与同步语言的应用框架下的数据交换,同时还能对外暴露接口,支持 http 协议,使得 nginx 能直接反向代理到 uwsgi,其实不用 nginx,uwsgi 本身就是个 http 协议服务器了,到了后来 async/await 这种基于事件循环的协程式语言出现,原来 wsgi 不够用了,所以就出了 asgi 协议,支持这个协议的中间件有 uvicorn 等,你说为啥需要 wsgi 协议,没有这个协议,你怎么拿到 http 协议 header 中的 Origin?