使用websockets和python/Django(/twisted?)

2024-03-29 13:19:52 发布

您现在位置:Python中文网/ 问答频道 /正文

websockets最有趣的部分是从服务器向浏览器发送本质上未经请求的内容,对吧?

嗯,我用的是Gregor müllegger的django websocket。在Django,让websockets工作是一个非常棒的早期尝试。

我已经完成了“hello world”,其工作方式是:当一个请求是websocket时,一个对象websocket被附加到请求对象上。因此,在解释websocket的视图中,我可以这样做:

request.websocket.send('We are the knights who say ni!')

那很好。我把这条信息像个魔咒一样放回浏览器。

但如果我想在不发出浏览器请求的情况下这样做呢?

好的,首先我将websocket保存在会话字典中:

request.session['websocket'] = request.websocket

然后,在shell中,我去按会话键获取会话。果然,会话字典中有一个websocket对象。快乐!

但是,当我试图这样做时:

>>> session.get_decoded()['websocket'].send('With a herring!')

我得到:

Traceback (most recent call last):
File "<console>", line 1, in <module>
error: [Errno 9] Bad file descriptor

悲伤。:-()

好吧,我对sockets的了解不多,但我知道在调试器中可以嗅探到,瞧,我看到调试器中的socket(它与请求中的真正websocket绑定)的fd=6,而我从会话保存的websocket中获取的socket的fd=-1。

一个面向套接字的人能帮我解决这个问题吗?


Tags: 对象服务器send内容字典websocketsrequestsession
3条回答

你可以给星际之门一次重击:http://boothead.github.com/stargate/http://pypi.python.org/pypi/stargate/

它建立在金字塔和eventlet之上(我也为eventlet提供了一点websocket支持和测试)。金字塔的最大优点是它有一个url映射到的资源的概念,而不仅仅是一个可调用的结果。因此,最终会得到一个映射到url结构的持久性资源图,而websocket连接只是路由并连接到这些资源。

所以你只需要做两件事:

class YourView(WebSocketView):

    def handler(self, websocket):
        self.request.context.add_listener(websocket)
        while True:
            msg = websocket.wait()
            # Do something with message

接收消息 以及

resource.send(some_other_message)

这里resource是上面stargate.resource.WebSocketAwareContext的一个实例(与self.request.context一样),send方法将消息发送到与add_listener方法连接的所有客户端。

要向所有连接的客户端发布消息,只需调用node.send(message)

我希望在接下来的一两周内能写一个小的示例应用程序来更好地演示这一点。

如果您需要帮助,可以在github上ping我。

我是django websocket的作者。我不是一个真正的专家,在网站和网络的话题,但我认为我有一个体面的理解正在发生的事情。抱歉,我说得太详细了。即使大多数答案不是针对你的问题的,它也可能在其他方面对你有所帮助。:-)


websockets的工作原理

让我简单地解释一下websocket是什么。websocket从一个看起来像是从浏览器建立的普通HTTP请求开始。它通过一个HTTP报头表示它希望将协议“升级”为websocket,而不是HTTP请求。如果服务器支持websockets,则它同意握手,并且服务器和客户端现在都知道它们将使用以前用于HTTP请求的已建立的tcp套接字作为交换websocket消息的连接。

除了发送和等待消息之外,它们当然还可以随时关闭连接。

django websocket如何滥用python的wsgi请求环境来劫持套接字

现在让我们详细了解一下django websocket如何在django请求-响应循环中实现HTTP请求的“升级”。

Django通常使用WSGI规范与apache或gunicorn等web服务器进行通信。该规范是在考虑到HTTP非常有限的通信模型的情况下设计的。它假设它获得一个HTTP请求(仅传入数据)并返回响应(仅传出数据)。这使得将django强制引入允许双向通信的websocket概念变得很棘手。

为了实现这一点,我在django websocket中所做的是深入研究WSGI和django的请求对象的内部,以检索底层套接字。然后使用这个tcp套接字直接将HTTP请求升级到websocket实例。

现在回答您原来的问题…

我希望上面清楚地表明,当websocket建立时,返回HttpResponse是没有意义的。这就是为什么通常不返回由django websocket处理的视图中的任何内容。

不过,我想紧跟视图的概念,视图保存逻辑并根据输入返回数据。这就是为什么您应该只使用视图中的代码来处理websocket。

从视图返回后,websocket将自动关闭。这样做是有原因的:我们不想让套接字在一段未定义的时间内保持打开,并且依赖客户端(浏览器)来关闭它。

这就是为什么不能在视图之外访问带有django websocket的websocket。文件描述符当然被设置为-1,表示它已经关闭。

免责声明

我在上面解释过,我正在django的周围环境中进行挖掘,以某种方式——以一种非常黑客的方式——访问底层套接字。这是非常脆弱的,也不应该工作,因为WSGI不是为这个设计的!我在上面还解释过,websocket在视图结束后关闭——然而,在websocket关闭(并关闭tcp套接字)之后,django的WSGI实现尝试发送HTTP响应——它不知道websocket s,并认为它处于正常的HTTP请求-响应周期。但是套接字已经关闭,发送将失败。这通常会导致django中出现异常。

这并没有影响我对开发服务器的测试。浏览器永远不会注意到(你知道。。套接字已经关闭;-)-但是在每个请求中引发一个未处理的错误不是一个很好的概念,并且可能会泄漏内存,无法正确处理数据库连接关闭,并且如果您使用django websocket进行更多的实验,那么很多athor事件在某个时刻都会中断。

这就是为什么我真的建议你不要使用websocket我们还有django呢。它不是故意的。Django,特别是WSGI,需要进行全面的检查来解决这些问题(参见this discussion for websockets and WSGI)。从那时起,我建议使用类似eventlet的方法。Eventlet有一个工作的websocket实现(我从Eventlet为django websocket的初始版本借用了一些代码),由于它只是简单的python代码,所以您可以从django导入模型和其他所有东西。唯一的缺点是需要另一个运行的webserver来处理websockets。

正如Gregor Müllegger所指出的,Websockets不能被WSGI正确地处理,因为该协议从未被设计来处理这样的特性。 uWSGI自1.9.11版以来,可以处理开箱即用的Websockets。在这里,uWSGI使用原始HTTP而不是WSGI协议与应用服务器通信。这样编写的服务器可以处理协议内部,并在很长一段时间内保持连接打开。使用Django视图处理长时间有效的连接也不是一个好主意,因为这样会阻塞工作线程,这是一个有限的资源。

Websockets的主要目的是让服务器以异步方式将消息推送到客户端。这可以是其他浏览器(如聊天客户端、多人游戏)触发的Django视图,也可以是Django芹菜(如sport results)触发的事件。因此,对于这些Django服务来说,使用消息队列将消息推送到客户机是最基本的。

为了以可伸缩的方式处理这个问题,我编写了django-websocket-redis,一个Django模块,它可以使用Redis作为后端消息队列,在一个线程/进程中打开所有那些长时间存在的Websocket连接。

相关问题 更多 >