Python 带龙卷风的WebSocket:从;“外部”;向客户端发送消息

Python 带龙卷风的WebSocket:从;“外部”;向客户端发送消息,python,websocket,webserver,tornado,Python,Websocket,Webserver,Tornado,我开始进入WebSocket,作为将数据从服务器推送到连接的客户端的方法。由于我使用python来编程任何类型的逻辑,所以到目前为止,我研究了Tornado。下面的代码片段显示了Web上随处可见的最基本示例: import tornado.httpserver import tornado.websocket import tornado.ioloop import tornado.web class WSHandler(tornado.websocket.WebSocketHandler):

我开始进入WebSocket,作为将数据从服务器推送到连接的客户端的方法。由于我使用python来编程任何类型的逻辑,所以到目前为止,我研究了Tornado。下面的代码片段显示了Web上随处可见的最基本示例:

import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print 'new connection'
        self.write_message("Hello World")

    def on_message(self, message):
        print 'message received %s' % message
        self.write_message('ECHO: ' + message)

    def on_close(self):
    print 'connection closed'


application = tornado.web.Application([
  (r'/ws', WSHandler),
])


if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
事实上,这是按预期进行的。然而,我不知道如何将这个“集成”到我的应用程序的其余部分。在上面的示例中,WebSocket只向客户端发送一些内容作为对客户端消息的回复。如何从“外部”访问WebSocket?例如,通知所有当前连接的客户端发生了某种类型的事件,而此事件不是来自客户端的任何类型的消息。理想情况下,我希望在我的代码中编写如下内容:

websocket_server.send_to_all_clients("Good news everyone...")

我该怎么做?或者我对WebSockets(或Tornado)的工作原理有完全的误解。谢谢

您需要跟踪所有连接的客户端。因此:

clients = []

def send_to_all_clients(message):
    for client in clients:
        client.write_message(message)

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        send_to_all_clients("new client")
        clients.append(self)

    def on_close(self):
        clients.remove(self)
        send_to_all_clients("removing client")

    def on_message(self, message):
        for client in clients:
            if client != self:
                client.write_message('ECHO: ' + message)

这是以汉斯·泰恩的例子为基础的。希望它能帮助您理解如何让服务器启动与客户机的通信,而不让客户机触发交互

这是服务器:

#!/usr/bin/python

import datetime
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web



class WSHandler(tornado.websocket.WebSocketHandler):
    clients = []
    def open(self):
        print 'new connection'
        self.write_message("Hello World")
        WSHandler.clients.append(self)

    def on_message(self, message):
        print 'message received %s' % message
        self.write_message('ECHO: ' + message)

    def on_close(self):
        print 'connection closed'
        WSHandler.clients.remove(self)

    @classmethod
    def write_to_clients(cls):
        print "Writing to clients"
        for client in cls.clients:
            client.write_message("Hi there!")


application = tornado.web.Application([
  (r'/ws', WSHandler),
])


if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=15), WSHandler.write_to_clients)
    tornado.ioloop.IOLoop.instance().start()
我将客户机列表设置为类变量,而不是全局变量。实际上,我不介意使用全局变量,但既然您关心它,这里有一种替代方法

下面是一个示例客户:

#!/usr/bin/python

import tornado.websocket
from tornado import gen 

@gen.coroutine
def test_ws():
    client = yield tornado.websocket.websocket_connect("ws://localhost:8888/ws")
    client.write_message("Testing from client")
    msg = yield client.read_message()
    print("msg is %s" % msg)
    msg = yield client.read_message()
    print("msg is %s" % msg)
    msg = yield client.read_message()
    print("msg is %s" % msg)
    client.close()

if __name__ == "__main__":
    tornado.ioloop.IOLoop.instance().run_sync(test_ws)
然后可以运行服务器,并连接两个测试客户机实例。执行此操作时,服务器将打印以下内容:

bennu@daveadmin:~$ ./torn.py 
new connection
message received Testing from client
new connection
message received Testing from client
<15 second delay>
Writing to clients
connection closed
connection closed
bennu@daveadmin:~$ ./web_client.py 
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 0
bennu@daveadmin:~$ ./web_client.py 
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 1
bennu@daveadmin:~$/py
新连接
从客户端接收到测试消息
新连接
从客户端接收到测试消息
给客户写信
连接关闭
连接关闭
第一个客户机打印以下内容:

bennu@daveadmin:~$ ./torn.py 
new connection
message received Testing from client
new connection
message received Testing from client
<15 second delay>
Writing to clients
connection closed
connection closed
bennu@daveadmin:~$ ./web_client.py 
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 0
bennu@daveadmin:~$ ./web_client.py 
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 1
bennu@daveadmin:~$/web\u client.py
msg是helloworld
msg是ECHO:来自客户端的测试
<15秒延迟>
你好!0
第二张打印的是:

bennu@daveadmin:~$ ./torn.py 
new connection
message received Testing from client
new connection
message received Testing from client
<15 second delay>
Writing to clients
connection closed
connection closed
bennu@daveadmin:~$ ./web_client.py 
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 0
bennu@daveadmin:~$ ./web_client.py 
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 1
bennu@daveadmin:~$/web\u client.py
msg是helloworld
msg是ECHO:来自客户端的测试
<15秒延迟>
你好!1.

在本例中,我只是让服务器延迟15秒将消息发送到客户端,但它可能会被您想要的任何东西触发。

我的解决方案是:首先将
“if uuuuuu name\uuuuu=='uuuu main\uuuu':”
-添加到main.py。然后将main.py导入websocket模块。e、 g.(
将main导入为MainApp
)。现在可以从ws.py/WebSocketHandler-function中调用“main.py”中的函数。-在处理程序内部,按如下方式传递消息:
MainApp.function(消息)

我不知道这是否是优雅的对立面,但它对我有效


…再加上创建一个自定义的“config.py”(看起来像:
someVar=int(0)
)并将其导入到“mainApp.py”。。类似于这样:
import config as cfg
-->现在,您可以在“main.py”中的函数中使用
cfg.someVar=newValue
更改变量,该函数曾经由处理程序从“ws.py”调用过。

Hans,谢谢!是的,我在某处找到了这个解决方案。但首先,有人认为,使用全局变量来实现这一点并不是一个好方法。第二,即使这样,我也不知道如何继续。我假设我必须在某个地方循环所有
客户机
,并为每个客户机调用
write\u message
方法。但是循环应该放在哪里。当我启动脚本时,所有内容似乎都被阻止了。您是否可以扩展我最初的示例,用您的方法制作一个完整的示例,包括客户端阵列上的循环。那太棒了!我添加了一个向所有客户端发送消息的方法,以及一些如何调用该消息的示例。我知道,但是,
send_to_all_clients
是从WSHandler类中调用的。现在,我如何从基本上无处不在的地方调用此方法以集成到更大的应用程序中?也许我只是对WebSocket有一个完全的误解。你可以在任何地方调用
send\u to\u all\u clients
。这可能会暴露我在Python方面的弱点:)。但不起作用的是,例如,第二个脚本带有来自。。。导入发送到所有客户端,然后导入发送到所有客户端(“外部消息”)。它只是不同的作用域,
clients
数组在这样调用时总是空的。你能举一个“你可以在任何地方打电话”的具体例子吗?达诺,非常感谢!这使我更接近我的目标。但是计时器不适合基于事件的系统。我也看不出,我怎么能把消息作为参数发送,而不是总是“嗨,那里”。你的“但它可能被你想要的任何东西触发”对我来说是真正有趣的部分:)。现在是否真的有办法获得WebSocket上的句柄,比如
ws=WebSocket()
,它包括在事件发生时启动服务器,然后启动
ws.write\u客户端(msg)
?这不是主要的想法吗?但我还没有找到一个例子。你心目中的“事件”是什么?我的示例可以稍微进行重构,这样
将客户机写入
只是模块顶层的一个函数,而不是一个类方法。它也可以使用一个参数,而不是总是使用“Hi,there”。然后您的事件处理代码就可以调用该方法。这有意义吗?我假设您的更改会像Hans修改的示例一样,在模块的顶层使用
send\u to\u all\u clients
方法。但在他的示例中,他仍然从WSHandler类中调用此方法。但是我如何从“无处不在”调用这个方法呢举个例子:假设我的应用程序进行了复杂的计算,每次有新的结果可用时,我都要向所有连接的客户端宣布它。我的意思不是让函数成为WSHandler类的一部分。只需将其作为模块顶层的独立函数,并使用
WSHandler.clients