websocket-python服务端

通过 socket.io 来操作 websocket API。

安装
1
pip install python-socketio

创建服务

创建服务的方式
1. 通过 WSGI 托管服务
1
2
3
4
5
6
7
import socketio

# 创建一个 socket.io 服务
sio = socketio.Server()

# 将 socket.io 服务托管到 wsgi 服务上
app = socketio.WSGIApp(sio)
2. 作为 Django、Flask 应用中的一部分
1
2
3
from wsgi import app  # a Flask, Django, etc. application

app = socketio.WSGIApp(sio, app)

不过,现在 django 或 flask 框架都有与之对应的 socket.io 扩展,直接使用即可。

3. 使用 eventlet 运行服务
1
2
3
4
5
6
7
8
9
import eventlet
eventlet.monkey_patch()

import socketio
import eventlet.wsgi

sio = socketio.Server(async_mode='eventlet') # 指明在evenlet模式下
app = socketio.WSGIApp(sio)
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)

解决跨域

由于安全原因,socket.io 采用了同源策略,因此需要解决跨域的问题。

很简单,只需在创建服务的时候添加:

1
sio = socketio.Server(async_mode='eventlet', cors_allowed_origins='*')

消息收发

事件

Socket.IO 协议是基于事件的。 当客户端想要与服务器通信时,它会发出一个事件。 每个事件都有一个名称和一个参数列表。 服务器使用 socketio.Server.event() 或 socketio.Server.on() 装饰器注册事件处理函数:

事件处理函数类似于 flask/django 中的视图函数,只不过不是通过 url 来调用对应的方法,而是通过事件名称来调用。

监听事件

监听事件用来接收消息。

1
2
3
4
5
6
7
@sio.event
def hello(sid, data):
return 'hello'

@sio.on('hello')
def hello(sid, data):
return 'hello'

@sio.event 修饰的函数的名称是事件名,@sio.on 修饰函数的参数是事件名称,该名称与客户端发送消息时使用的事件名称一致。

注意:‘connect’,’disconnect’,’message’ 是特殊的事件,当连接成功,连接失败,接收到消失时会自动调用,除非你想自定义它们的内部代码逻辑,否则应避免用它们作为事件名。

事件处理函数参数介绍:

  • sid:session_id,是客户端的身份标识。
  • data:客户端发送过来的数据,数据类型与客户端发送的数据类型一致。

提交事件

我们可以向客户端发送事件,从而将一些数据传递到客户端。

1
sio.emit('hello', {'data': 'hello world'})

第一个参数是事件名,第二个参数是数据载荷,他可以接受的类型有 strbyteslist,,dict 或者 tuple


命名空间

Socket.IO 协议支持多个逻辑连接,全部复用在同一个物理连接上。 客户端可以通过在每个连接上指定不同的命名空间来打开多个连接。 命名空间由客户端作为主机名和端口之后的路径名给出。 例如,连接到 http://localhost:8000/chat 将打开到命名空间 /chat 的连接。

每个命名空间都独立于其他命名空间进行处理,具有单独的会话 ID (sids)、事件处理程序和房间。

1
2
3
4
@sio.on('hello', namespace='/chat')
def hello(sid, data):
print(data)
sio.emit('hello', {'sid': sid, 'data': data['data']}, namespace='/user')

客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
import {io} from 'socket.io-client';

const socket = io('http://localhost:8000/chat');
// 同时打开多个连接
const user_socket = io('http://localhost:8000/user');

socket.on('hello', (res) => {
if(res.sid === socket.id) {
this.messages.push(res.data);
}
});

socket.emit('hello', {data: this.message});

房间

为了方便服务器向相关客户端组发送事件,应用程序可以将其客户端放入“房间”,然后将消息发送到这些房间。

socketio.SocketIO.emit() 方法的 room 参数用于将特定客户端指定为事件的接收者。 这是因为在连接时,会为每个客户端创建一个个人房间,并使用分配给连接的 sid 命名。 然后应用程序可以自由地创建额外的房间并使用 socketio.Server.enter_room() 和 socketio.Server.leave_room() 方法管理其中的客户端。 客户可以根据需要在多个房间中,并且可以根据需要经常在房间之间移动。

1
2
3
4
5
6
7
@sio.event
def begin_chat(sid):
sio.enter_room(sid, 'chat_users')

@sio.event
def exit_chat(sid):
sio.leave_room(sid, 'chat_users')

如果我们想要向一个房间中的所有连接发送消息,可以这样做:

1
2
3
@sio.event
def my_message(sid, data):
sio.emit('my reply', data, room='chat_users', skip_sid=sid)

skip_sid 指明不向某个连接发送消息。这也是 scoket.io 的广播机制。

1
2
3
4
5
// 返回该连接所在房间的列表
rooms = sio.rooms(sid)

// 移除该房间中的所有连接
sio.close_room(room='')

更多内容请看 python-socketio


flask 中使用 socketio

安装
1
pip install flask_socketio
配置

webapp/init.py

1
2
3
4
5
6
7
8
from flask_socketio import SocketIO

sio = SocketIO(cors_allowed_origins='*', async_mode='eventlet', max_http_buffer_size=1e20)

def createApp():
...
sio.init_app(app)
...

app.py

1
2
3
4
5
6
7
8
9
10
from webapp import createApp, sio
from flask_cors import CORS


app = createApp()
CORS(app, supports_credentials=True, max_age='2592000')


if __name__ == '__main__':
sio.run(app)

这里只是将 app.run() 改为 sio.run(),不影响其启动和运行。

注意:

  • development server 启用不了 eventlet,传输协议只能是 polling 而不是 websocket。因此,在开发期间去掉 async_mode 参数,在部署时配合 gunicorn 即可使用 eventlet。

  • socketio 默认单个消息最大数据量为 1M,需要通过 max_http_buffer_size 参数将此限制放宽。

详细内容请见:https://flask-socketio.readthedocs.io/en/latest/deployment.html。