python-socketio 文档翻译

464 阅读12分钟

入门

什么是Socket.IO?

Socket.IO 是一种传输协议,可实现客户端(通常是web浏览器)和服务器之间基于事件的实时双向通信。客户端和服务器组件的官方实现是用JavaScript编写的。这个包提供了两者的Python实现,每个实现都有标准和异步变体。

客户端例子

下面的例子展示了一个简单的 Python 客户端:

import socketio

sio = socketio.Client()

@sio.event
def connect():
    print('connection established')
    
@sio.event
def my_message(data):
    print('message received with', data)
    sio.emit('my response', {'response': 'my response'})
    
@sio.event
def disconnect():
    print('disconnect')

sio.connect('http://localhost:8000')
sio.wait()

下面的是一个相似的使用 asyncio(编写并发代码的库,使用 async/await 语法)的客户端:

import asyncio
import socketio

sio = socketio.AsyncClient()

@sio.event
async def connect():
    print('connection established')

@sio.event
async def my_message(data):
    print('message received with ', data)
    await sio.emit('my response', {'response': 'my response'})

@sio.event
async def disconnect():
    print('disconnected from server')

async def main():
    await sio.connect('http://localhost:5000')
    await sio.wait()

if __name__ == '__main__':
    asyncio.run(main())

使用基于事件的体系结构,该体系结构由隐藏协议细节的装饰器实现;实现HTTP长轮询和WebSocket传输;如果断开连接,则自动重新连接到服务器。

服务器例子

以下应用程序是使用Eventlet异步服务器的基本服务器示例

import eventlet
import socketio

sio = socketio.Server()
app = socketio.WSGIApp(sio, static_files={
    '/': {'content_type': 'text/html', 'filename': 'index.html'}
})

@sio.event
def connect(sid, environ):
    print('connect ', sid)

@sio.event
def my_message(sid, data):
    print('message ', data)

@sio.event
def disconnect(sid):
    print('disconnect ', sid)

if __name__ == '__main__':
    eventlet.wsgi.server(eventlet.listen(('', 5000)), app)

可以与在Flask、Django等框架中编写的WSGI应用程序集成。

Socket.IO 客户端

包提供两种 Socket.IO客户端:

  • 一个“简单的”客户端,它提供了一个简单的API,对大多数应用程序来说就足够了。
  • 一个“事件驱动”的客户端,它提供了对 Socket.IO 协议所有功能的访问。

这些客户端中的每一个都有两种变体:一种用于标准Python库,另一种用于使用asyncio包构建的异步应用程序。

使用简单的客户端

简单客户端的优势是它抽象掉了维护Socket.IO连接所需的逻辑。该客户端以完全透明的方式处理断开连接和重新连接,而不会给应用程序增加任何复杂性。客户端以完全透明的方式处理断开连接和重新连接,而不会给应用程序添加任何复杂性。

创建客户端的例子

新建一个Socket.IO的最简单的方法是使用上下文管理器接口(context manager):

import socketio

# asynicio
asynic with socketio.AsyncSimpleClient() as sio:
    # ... 连接到一个服务器,并使用客户端
    # ... 无需手动断开连接

使用以上用法,上细纹管理器将爆炸嗯客户端在离开 withasync with之前完全断开连接。

如果有意愿,也可以手动实例化客户端:

import socketio

# asyncio
sio = socketio.AsyncSimpleClient()

连接到服务器

与服务器的连接是通过调用connect()方法建立的。

asyncio 客户端的情况下,该方法是一个协同程序(coroutine):

await sio.connect('http://localhost:5000')

默认情况下,客户端首先使用长轮询传输连接到服务器,然后尝试升级连接以使用WebSocket。要使用WebSocket直接连接,请使用transports参数:

await sio.connect('http://localhost:5000', transports=['websocket'])

连接之后,服务器立刻给客户端分配一个唯一的会话标识符(session identifier)。应用程序可以从 sid 属性获取此标识符:

print(f"My sid is {sio.sid}")

连接中使用的Socket.IO传输(transport)可以从传输属性中获得:

print('my transport is', sio.transport)

传输是一个字符串,它的值是websocket, polling的其中之一。

TLS/SSL 支持

客户端支持 TLS/SSL 连接。要启用它,使用 https:// 连接 URL。

await sio.connect('https://example.com')

发送事件

客户端可以通过 emit() 方法发送事件到服务器。

await sio.emit('my message', {'foo': 'bar'})

该方法需要的参数是要发送事件的名称和传递给服务器的可选数据。数据类型可以是str, bytes, dict, list 或者 tuple

接收事件

客户端通过receive()方法等待服务器发送事件:

event = await sio.receive()
print(f"recived event: {event[0]} with {event[1:]}")

receive()返回的数据是一个列表。列表的第一个是事件名称,其余各项是服务器传递的参数。

上述方法中,receive()方法只有在接收到一个从服务器接发送的事件时才返回。可以通过以秒为单位的可选超时(timeout)来防止客户端永远等待:

from socketio.exceptions import TimeoutError

try:
    event = await sio.event(timeout=5)
except TimeoutError:
    print('timed out waiting for event')
else:
    print(f'received event: {event}')

从服务器断开连接

客户端可在任何时间调用disconnect()方法,请求服务器断开连接:

await sio.disconnect()

Debug和排查问题

为了帮助你debug问题,可以配置客户端向终端发送日志:

import socketio

sio = socketio.AsyncClient(logger=True, engineio_logger=True)

这部分没翻译,以后补

使用事件驱动客户端

创建一个客户端实例

为了实例化一个Socket.IO客户端,简单地创建一个适当客户端类的实例:

import socketio

# 标准Python
sio = socketio.Clinet()

# 异步
sio = socketio.AsyncClient()

定义一个事件处理器

Socket.IO 协议是基于事件的。当一个服务器希望与一个客户端交流时,它发送一个事件。每个事件都有一个名字,和一个由参数组成的列表。客户通过 socketio.Client.event()或者socketio.Client.on()装饰器注册事件处理函数。

@sio.event:
def message(data):
    print('I received a message!')
    
@sio.on('my message')
def on_message(data):
    print('I received a message!')

第一个例子中,事件名称从处理函数中获取。第二个例子稍微详细一些,但它允许事件名称与函数名称不同,或者在函数名称中包含非法字符,如空格。

Catch-All 事件与命名空间处理器

一个“catch-all”事件处理器可以被任何没有事件处理器的事件调用。你可以用*作为事件名称,来定义一个catch-all处理器:

@sio.on('*')
def any_event(event, sid, data):
    pass

一个 catch-all 事件处理器将接收的第一个参数当作事件名称。余下的参数与常规事件处理程序的参数相同。

connect事件和disconnet事件必须被明确的定义,而不能被一个 catch-all 事件处理器调用。

同样地,一个“catch-all”命名空间处理器可以被任何没有显示定义事件处理器的,连接的命名空间调用。使用*代替命名空间,就像catch-all事件一样:

@sio.on('my_event', namespace='*')
def my_event_any_namespace(namespace, sid, data):
    pass

对于这些事件,第一个被传递的参数是命名空间,接下来是事件的一般参数。

最后,对所有的事件和所有的命名空间,定义一个“catch-all”处理器,是可行的:

@sio.on('*', namespace='*')
def any_event_any_namespace(event, namespace, sid, data):
    pass

该处理器会把接收到的第一和第二个参数当作事件名称和命名空间。

连接,连接出错和断开事件处理器

connect, connect_errordisconnect事件是特殊的;当一个客户端与服务器连接或断开连接时,它们会被自动地调用:

@sio.event
def connect()
    print("I'm connected!")
    
@sio.event
def connect_error(data):
    print("The connection failed!")
    
@sio.event
def disconnect():
    print("I'm disconnected!")

当一个连接尝试失败时,connect_error处理器会被调用。如果服务器提供了参数,这些参数将传递给处理器。服务器可以使用参数向客户端提供有关连接失败的信息。

disconnect处理器在应用程序启动断开连接、服务器启动断开连接或意外断开连接(例如网络故障)时被调用。在意外断开连接时,客户端将在调用disconnect处理器后立刻尝试重新连接。一旦连接重新建立,connect处理器将被再一次调用。

connect, connect_error, disconnect 事件必须被明确定义,并且不能被catch-all事件处理器调用。

连接到一个服务器

与服务器之间连接的建立是依靠调用connect方法的:


# 标准python
sio.connect('http://localhost:5000')

# 异步
await sio.connect('http://localhost:5000')

连接后,服务器会为客户端分配一个唯一的会话标识符。应用程序可以在sid属性中找到此标识符:

print(f'My sid is {sio.sid}')

连接中使用的Socket.IO transport 可以从transport属性中获得:

print(f'My transport is {sio.transport}')

transport属性的值是websocketpolling

TLS/SSL 支持

客户端支持 TLS/SSL 连接。如果启用它,使用 https:// 连接URL:

# 标准python
sio.connect('https://example.com')
# 异步
await sio.connect('https://example.com')

接下来有一部分没翻译,之后补。

发送事件

客户端可以使用emit()方法发送一个事件到服务器:

# 标准python
sio.emit('My message', {'foo': 'bar'})
# 异步
await sio.emit('My message', {'foo': 'bar'})

该方法所需的参数有要发送事件的名称,和要传递给服务器的可选数据。数据类型可以是 str, bytes, dict, list 或者 tuple。当传递list或者tuple时,其内部各项必须是非tuple的允许传递的类型。使用元组时,元组的元素将作为单独的参数传递给服务器端事件处理程序函数。

emit()方法可以在事件处理程序内部调用,作为对服务器事件的响应,也可以在应用程序的任何其他部分调用,包括在后台任务中调用。

事件回调

当服务器发送一个事件到客户端时,它可以选择性地提供回调函数,作为确认服务器已处理该事件的一种方式进行调用。虽然这完全由服务器管理,但客户端可以提供一个返回值列表,这些值将传递给服务器设置的回调函数。这只需从处理程序函数返回所需的值即可实现:

def my_event(sid, data):
    # handle the message
    return "Ok", 123

同样,客户端可以在服务器处理完事件后请求调用回调函数。socketio.Server.emit()方法有一个可选的callback参数,可以设置为可调用函数。如果提供了该参数,则在服务器处理完事件后,此函数会被调用,并且服务器处理器返回的所有值都会作为参数传递给此方法。

命名空间(Namespaces)

Socket.IO 协议支持多个逻辑连接,所有逻辑连接都在同一物理连接上多路传输。客户端可以通过在每个连接上指定不同的命名空间来打开多个连接。命名空间使用以正斜杠开头的路径语法。名称空间列表可以由客户端在connect()调用中提供。下面的示例创建两个逻辑连接,一个默认的连接加上在/chat下的第二个连接:

sio.connect('http://localhost:5000', namespaces=['/chat'])

要在命名空间上定义事件处理程序,必须将namespace参数添加到相应的装饰器中:

@sio.event(namespace='/chat')
def my_custom_event(sid, data):
    pass

@sio.on('connect', namespace='/chat')
def on_connect():
    print("I'm connected to the /chat namespace!")

同样,客户端在emit()方法提供命名空间,就可以在一个命名空间上发送事件到服务器:

sio.emit('my message', {'foo': 'bar'}, namespace='/chat')

如果connect()方法的namespace参数未给出,事件处理器内的使用的任何命名空间将被自动连接。

基于类的命名空间

作为基于装饰器的事件处理器的替代方案,可以将属于命名空间的事件处理器创建为socketio.ClientNamespace的子类的方法:

    def on_connect(self):
        pass
        
    def on_disconnect(self):
        pass
        
    def on_my_event(self, data):
        self.emit('my response', data)

sio.register_namespace(MyCustomNamespace('/chat'))

一个基于类的catch-all命名空间处理器可以通过在注册时传递*作为命名空间来定义:

sio.register_namespace(MyCustomNamespace('/chat'))

当使用基于类的命名空间时,客户端接收的任何事件,会被派送给一个名字为事件名称与on_前缀组合的方法。例如,事件my_event将会被一个名为on_my_event的方法处理。如果接收到的事件在命名空间类中没有定义相应的方法,则忽略该事件。在基于类的命名空间中使用的所有事件名称都必须使用方法名称中合法的字符。

为了方便在基于类的命名空间中定义的方法,命名空间实例包括socketio.Client和socketio.AsyncClient类中的几个方法的版本,这些方法在未给定命名空间参数时默认为正确的命名空间。

如果一个事件在基于类的命名空间中有一个处理程序,同时也有一个基于装饰器的函数处理程序,则只调用独立函数处理程序。

从服务器断开连接

客户端可以在任何时间调用disconnect()方法来请求断开与服务器之间的连接。

# 标准Python
sio.disconnect()

# 异步
await sio.disconnect()

管理后台任务

当客户端与服务器建立连接时,将生成一些后台任务,以保持连接有效并处理传入事件。在主线程上运行的应用程序可以自由地执行任何工作,因为这不会阻止Socket.IO客户端的运行。

如果应用程序在主线程中没有任何事情要做,只想等待与服务器的连接结束,它可以调用wait()方法:

# 标准Python
sio.wait()

# 异步
await sio.wait()

为了应用程序的方便,一个助手函数被提供用来启动自定义后台任务:

def my_background_task(my_argument):
    # do some background work here!
    pass
    
task = sio.start_background_task(my_background_task, 123)

传递给此方法的参数是后台函数以及用于调用函数的任何位置或关键字参数。

sleep()方法是第二个方便函数,它是为处理自己的后台任务的应用程序提供的:

sio.sleep(2)

传递给该方法的单个参数是睡眠的秒数。

Debug和排查问题

没翻译,之后补

Socket.IO 服务器

此库包含两种Socket.IO服务器:

  • socketio.Server() 类创建了一个与Python标准库兼容的服务器。

  • socketio.AsyncServer()类创建一个与asyncio包兼容的服务器。