Skip to main content
 首页 » 编程设计

python之uWSGI:通过 websockets 与多个客户端一起工作

2024年10月25日36Leo_wl

我是 uWSGI 的新手,我正在开发一个需要大量网络套接字通信的网络应用程序。我决定通过创建一个简单的聊天应用程序来练习。

来自 uWSGI docs :

def application(env, start_response): 
    # complete the handshake 
    uwsgi.websocket_handshake(env['HTTP_SEC_WEBSOCKET_KEY'], env.get('HTTP_ORIGIN', '')) 
    while True: 
        msg = uwsgi.websocket_recv() 
        uwsgi.websocket_send(msg) 

我不想使用这种方法,因为

  1. 只有在客户向您发送了一些内容后,您才能向其发送消息;
  2. 您无权访问其他 websocket 连接,因此基本上您只能与 1 个客户端通信。

但是同一页面有一个 example如何实现聊天。这会很棒,但他们在示例中使用了 Redis:

    r = redis.StrictRedis(host='localhost', port=6379, db=0) 
    channel = r.pubsub() 
    channel.subscribe('foobar') 
 
    websocket_fd = uwsgi.connection_fd() 
    redis_fd = channel.connection._sock.fileno() 
 
    while True: 
        uwsgi.wait_fd_read(websocket_fd, 3) 
        uwsgi.wait_fd_read(redis_fd) 
        uwsgi.suspend() 

据我所知,这里的Redis被用作一个外部服务器,允许不同的uWSGI请求处理程序使用相同的数据。

这真的需要那么困难吗?

看看使用 Node.js 的聊天解决方案(示例取自 javascript.ru):

var WebSocketServer = new require('ws'); 
 
// connected clients 
var clients = {}; 
 
// WebSocket-server serves 8081 port 
var webSocketServer = new WebSocketServer.Server({ 
  port: 8081 
}); 
webSocketServer.on('connection', function(ws) { 
 
  var id = Math.random(); 
  clients[id] = ws; 
  console.log("new connection " + id); 
 
  ws.on('message', function(message) { 
    console.log("recieved a new message: " + message); 
 
    for (var key in clients) { 
      clients[key].send(message); 
    } 
  }); 
 
  ws.on('close', function() { 
    console.log("connection closed " + id); 
    delete clients[id]; 
  }); 
 
}); 

我真正喜欢的是

  1. 基于事件的方法让我可以随时向客户发送数据
  2. 所有客户端都存储在一个简单的clients 字典中。我可以轻松访问它,无需使用某些外部服务器在客户端之间交换数据。

为了解决 uWSGI 中的第一个问题(非阻塞的基于事件的方法)我写了这个代码片段:

import uwsgi 
from threading import Thread 
 
 
class WebSocket(Thread): 
    def __init__(self, env): 
        super().__init__() 
        self.listeners = [] 
        self._env = env 
 
    def run(self): 
        self._working = True 
        uwsgi.websocket_handshake( 
            self._env['HTTP_SEC_WEBSOCKET_KEY'], self._env.get('HTTP_ORIGIN', '')) 
 
        while self._working: 
            msg = uwsgi.websocket_recv() 
            for listener in self.listeners: 
                listener(msg) 
 
    def send(self, msg): 
        uwsgi.websocket_send(msg) 
 
    def close(self): 
        self._working = False 

所以我的第一个问题是这是否有效。

第二个问题是我如何在请求处理程序之间交换数据。我觉得我完全误解了 uWSGI 设计。 我使用 uwsgi --http :80 --wsgi-file=main.py --master --static-map/st=web-static 来测试我的应用程序。理想情况下,我会在 main.py 中定义一个对象并使用它,但我假设这个 main.py 将在不同的工作线程/线程/进程中多次初始化.

我已经看到一个关于数据交换的类似问题:Communication between workers in uwsgi

答案是

Pay attention, it will works only if you have a single worker/process. Another common approach is using the uWSGI caching framework (the name is misleading, infact is a shared dictionary). It will allows you to share data between workers and threads.

我将此 uWSGI 缓存框架视为某种独立的外部数据存储(参见上面的 Redis 示例)。但在我看到 Node.js 上的简洁实现后,我不想不使用缓存框架,而是希望在所有请求处理程序之间共享相同的 python 对象。

请您参考如下方法:

首先,您应该投资学习同步与异步编程范式。节点方法似乎更容易,但这只是因为您需要管理一个进程。如果您需要扩展(到多台机器或简单的多进程),您又回到了“python 问题”。拥有外部 channel (如 Redis)是一种常见模式,您应该使用它,因为它可以让您轻松扩展。

关于 python、uWSGI 和 websockets,我强烈建议你看看 gevent。 uWSGI websockets 系统支持它并且那里有很多例子。您将能够提高并发性,而无需依赖基于回调的编程。

最终(但前提是您喜欢回调)您可以看看 tornado。