问题描述
我只是以“异步”和“线程”的基本知识跳入websocket编程,我有类似的东西
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import socket
import uuid
import json
import datetime
class WSHandler(tornado.websocket.WebSocketHandler):
clients = []
def open(self):
self.id = str(uuid.uuid4())
self.user_info = self.request.remote_ip +' - '+ self.id
print (f'[{self.user_info}] Conectado')
client = {"sess": self,"id" : self.id}
self.clients.append(client.copy())
def on_message(self,message):
print (f'[{self.user_info}] Mensaje Recivido: {message}')
print (f'[{self.user_info}] Respuesta al Cliente: {message[::-1]}')
self.write_message(message[::-1])
self.comm(message)
def on_close(self):
print (f'[{self.user_info}] Desconectado')
for x in self.clients:
if x["id"] == self.id :
self.clients.remove(x)
def check_origin(self,origin):
return True
application = tornado.web.Application([
(r'/',WSHandler),])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(80)
myIP = socket.gethostbyname(socket.gethostname())
print ('*** Websocket Server Started at %s***' % myIP)
tornado.ioloop.IOLoop.instance().start()
我的问题是我应该在哪里添加代码?我应该在WShandler类中还是在外部或其他文件中添加所有内容?以及何时使用@classmethod?现在,当我在处理程序中添加代码但我只有几个测试客户端时,代码就没有问题。
解决方法
可能不是完整的解决方案,而只是一些想法。
您也许可以看一下龙卷风网络套接字聊天示例, here。
第一个好的变化是,他们的客户(侍者)是set() 确保默认情况下每个客户端仅包含一次。并且它被定义和访问为类变量。因此,您将不使用self.waiters而是使用cls.waiters或ClassName.waiters(在本例中为ChatSocketHandler.waiters)来访问它。
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
waiters = set()
第二个变化是他们更新了每个客户端(您可以在此处选择 因为@classmethod不会将更新发送给所有人,而是仅发送给部分),因为 他们不想接收实例(自己),但不想接收类(cls)和 引用类变量(在这种情况下,是服务员,缓存和cach_size)
我们可以在这里忘记缓存和缓存大小。
像这样:
@classmethod
def send_updates(cls,chat):
logging.info("sending message to %d waiters",len(cls.waiters))
for waiter in cls.waiters:
try:
waiter.write_message(chat)
except:
logging.error("Error sending message",exc_info=True)
每次调用API时,都会创建一个新的处理程序实例,称为self
。 self
中的每个参数实际上对于实例都是唯一的,并且与实际的客户端相关,从而调用您的方法。识别每个呼叫的客户很好。
因此,基于实例的客户端列表(如(self.clients))在每次调用时始终为空。添加客户只会将其添加到该实例的世界视图中。
但是有时您希望拥有一些变量,例如客户端列表,这些变量对于从您的类创建的所有实例都是相同的。
这是类变量(您直接在类定义下定义的变量)和@classmethod
装饰器起作用的地方。
@classmethod
使方法调用独立于实例。这意味着您只能在那些方法中访问类变量。但是在
消息代理,这几乎就是我们想要的:
-
将客户端添加到类变量,该变量对于处理程序的所有实例都是相同的。而且由于它是一个集合,所以每个客户端都是唯一的。
-
接收消息时,将其发送给所有(或部分客户)
-
所以
on_message
是一个“普通”实例方法,但它调用的内容类似于:send_updates()
,最后是@classmethod
。 -
send_updates()遍历所有(或子集)客户端(侍者),并使用它最终发送实际更新。
从示例:
@classmethod
def send_updates(cls,exc_info=True)
请记住,您已经在waiters.append(self)中添加了服务员,因此每个服务员实际上都是一个实例,并且您“简单地”调用该实例(该实例代表一个调用者)write_message()方法。因此,此消息不会广播,而是一一发送给每个呼叫者。在这里您可以按某些条件(例如主题或小组)分开...
因此,简而言之:对于独立于特定实例(例如您的调用方或客户端)的方法使用@classmethod
,并且您想对自己的“全部”或“全部”子集进行操作客户。但是您只能在那些方法中访问类变量。因为这是他们的目的,所以应该没问题;)