【问题标题】:Making moves w/ websockets and python / django ( / twisted? )使用 websockets 和 python / django ( / twisted? ) 移动
【发布时间】:2011-05-20 19:38:13
【问题描述】:

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

好吧,我正在使用 Gregor Müllegger 的 django-websocket。这是使 websockets 在 Django 中工作的一个非常好的早期破解。

我已经完成了“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

伤心。 :-(

好的,所以我对套接字知之甚少,但我知道的足以在调试器中四处嗅探,瞧瞧,我看到调试器中的套接字(它与真正的 websocket 绑定请求)具有 fd=6,而我从会话保存的 websocket 中获取的具有 fd=-1。

有面向套接字的人可以帮我解决这些问题吗?

【问题讨论】:

  • @EliCourtwright 你还没有奖励赏金..
  • @paddila:是的,即使您提供开放赏金的原因是“一个或多个答案具有示范性,值得额外赏金”,它也会让您等待 24 小时。只要系统允许,我就会奖励赏金。

标签: python django sockets websocket


【解决方案1】:

正如 Gregor Müllegger 所指出的,WSGI 无法正确处理 Websocket,因为该协议从未设计用于处理此类功能。 uWSGI,从 1.9.11 版开始,可以开箱即用地处理 Websocket。这里 uWSGI 使用原始 HTTP 而不是 WSGI 协议与应用程序服务器通信。因此,以这种方式编写的服务器可以处理协议内部并在很长一段时间内保持连接打开。由 Django 视图处理长期存在的连接也不是一个好主意,因为它们会阻塞工作线程,这是一种有限的资源。

Websockets 的主要目的是让服务器以异步方式向客户端推送消息。这可以是由其他浏览器触发的 Django 视图(例如:聊天客户端、多人游戏),或由 django-celery 触发的事件(例如:运动结果)。因此,这些 Django 服务的基础是使用消息队列将消息推送到客户端。

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

【讨论】:

  • 感谢您对这个老化问题的见解!
  • +1 for django-websocket-redis,一旦您熟悉了建议的 nginxuWSGI 组合,安装/使用非常简单
【解决方案2】:

你可以给星际之门一个狂欢: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)

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

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

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

如果您需要帮助,请随时在 github 上联系我。

【讨论】:

  • Ben - 这看起来很乐观。你引起了我的注意! :-)
【解决方案3】:

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


websocket 的工作原理

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

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

django-websocket如何滥用python的wsgi请求环境劫持socket

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

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

我在 django-websocket 中为实现这一点所做的是我非常深入地挖掘 WSGI 和 django 的请求对象的内部结构以检索底层套接字。然后使用这个 tcp 套接字直接处理升级到 websocket 实例的 HTTP 请求。

现在回答您最初的问题...

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

但是,我想坚持视图的概念,该视图包含逻辑并根据输入返回数据。这就是为什么您应该只使用视图中的代码来处理 websocket。

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

这就是为什么您无法在视图之外使用 django-websocket 访问 websocket。然后文件描述符当然设置为 -1,表示它已经关闭。

免责声明

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

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

这就是为什么我真的建议你不要在 django 中使用 websockets。它不是按设计工作的。 Django,尤其是 WSGI 需要彻底检修来解决这些问题(参见 this discussion for websockets and WSGI)。从那时起,我建议使用eventlet 之类的东西。 Eventlet 有一个有效的 websocket 实现(我从 eventlet 借了一些代码用于 django-websocket 的初始版本),因为它只是普通的 python 代码,你可以从 django 导入你的模型和其他所有东西。唯一的缺点是您需要运行第二个网络服务器来处理网络套接字。

【讨论】:

  • 感谢您的精彩回答。我等着接受它,直到我可以给你一个赏金。 :-)
  • 这是否意味着要为 websocket 提供服务,应该简单地保持打开请求?如果消息迭代器在等待传入消息时阻塞,您将如何发送未经请求的消息?
  • @Thomas 是的,您应该保持请求打开(因为它是 websocket 的套接字 - 有意义吗?)。等待消息时不需要阻塞,可以使用read()has_messages() 方法定期检查新事物。在此处阅读自述文件:github.com/gregmuellegger/django-websocket 然后您当然可以随时触发发送消息。
  • 我认为这很重要:Eventlet 有一个有效的 websocket 实现,因为它只是简单的 python 代码,你可以从 django 导入你的模型和其他所有东西:你可以像使用 django 一样使用库,但不是请求、响应、中间件、...部分。
【解决方案4】:

request.websocket 可能在您从请求处理程序(视图)返回时关闭。简单的解决方案是让处理程序保持活动状态(通过不从视图返回)。如果您的服务器不是多线程的,您将无法接受任何其他并发请求。

【讨论】:

  • 但是那有什么意义呢?如果我需要在视图中保持请求的存在,我马上回到 Comet。 websockets 的全部意义在于能够将内容推送到浏览器,而不必保持请求处于活动状态,不是吗?
  • @Justin Myles Holmes:阅读pypi.python.org/pypi/django-websocket上的免责声明
  • 是的,我看到了。没问题,有两个原因:1)我正在使用 django-websocket 附带的多线程运行服务器,2)我已完全准备好使用 websocket-ready 的服务器解决方案;使用 pywebsocket (blegh) 或 Twisted (yay) 的 Apache / mod_python - 但目前我什至无法对其进行测试。我需要更多地了解为什么我无法从 shell 中使用这个套接字。
猜你喜欢
  • 1970-01-01
  • 2011-05-23
  • 1970-01-01
  • 1970-01-01
  • 2017-07-05
  • 2012-10-29
  • 2020-06-09
  • 2011-05-30
  • 2014-09-07
相关资源
最近更新 更多