带标准输入的 Tornado 客户端
Tornado client with stdin
我正在尝试构建一个使用 Python 的多人游戏。我正在使用 Tornado 构建客户端和服务器。 理想情况下,我希望发生的事情如下:
(a) 客户端等待用户从命令行输入
(b) 当客户端获得用户输入时,将用户输入发送给服务器
(c) 服务器在其上模拟一些处理(这将是游戏引擎)并将响应发送回客户端。
服务器
"""
Server module for game server
"""
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import uuid
import json
class Controller(object):
def __init__(self):
self.players = ()
self.connections = ()
def check(self):
return "Hi"
def run_somthing(self, text):
new_text = "Server: " + text
return new_text
class InitMessageHandler(tornado.web.RequestHandler):
def get(self):
user_data = {}
user_data['user_id'] = str(uuid.uuid4())
self.write(json.dumps(user_data))
class GameHandler(tornado.websocket.WebSocketHandler):
def open(self):
# called anytime a new connection with this server is opened
print("Client connected")
print("Client sent: ", self)
if seif not in self.application.controller.connections:
self.application.controller.connections.add(self)
def on_message(self):
# called anytime a new message is received
pass
def check_origin(self, origin):
return True
def on_close(self):
# called a websocket connection is closed
if self in self.application.controller.connections:
self.application.controller.connections.remove(self)
class Server(tornado.web.Application):
def __init__(self):
self.controller = Controller()
handlers = [
(r"/join", InitMessageHandler),
(r"/game", GameHandler)
]
tornado.web.Application.__init__(self, handlers)
if __name__ == "__main__":
tornado.options.parse_command_line()
try:
application = Server()
server = tornado.httpserver.HTTPServer(application)
server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
print("closed")
客户
"""
Client module for the game clients(Players)
"""
import tornado.ioloop
import tornado.websocket
import requests
import json
import sys
import tornado.gen
class Client(object):
def __init__(self, join_url, play_url):
self.wsconn = None
self.join_url = join_url
self.play_url = play_url
#self.io_loop = tornado.ioloop.IOLoop.instance()
#self.io_loop.add_handler(sys.stdin, self.handle_user_input, tornado.ioloop.IOLoop.READ)
self.user_details = {}
self.has_initialised = False
#self.io_loop.start()
self.main()
def get_user_input(self, question=None):
str_choice = input(question)
while any((str_choice is None, not str_choice.strip())):
print("You entered an empty answer")
str_choice = input(question)
return str_choice
def _merge_dicts(*dict_args):
"""
Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
def generate_wsmessage(self):
msg_line = input("Enter message to send to server")
while any((msg_line is None, not msg_line.strip())):
print("You entered an empty answer")
msg_line = input("Enter message to send to server")
msg = {}
msg['message'] = msg_line
msg_to_send = self._merge_dicts(self.user_details, msg)
return json.dumps(msg_to-send)
def init(self):
print("Heh")
username = self.get_user_input("What is your username? ")
print("Getting initial user details")
req = requests.get(self.join_url)
response = json.loads(req.text)
print(response)
self.user_details['name'] = username
self.user_details['user_id'] = response['user_id']
self.has_initialised = True
def server_recv(self, msg):
print("Server has connected on websocket socket with msg=", msg)
@tornado.gen.coroutine
def connect_on_websocket(self):
try:
self.wsconn = yield tornado.websocket.websocket_connect(self.play_url, on_message_callback=self.server_recv)
except Exception as e:
print("Connection error: {}".format(e))
else:
print("Connected")
@tornado.gen.coroutine
def send_wsmessage(self):
msg = self.generate_wsmessage()
yield self.wsconn.write_message(msg)
@tornado.gen.coroutine
def communicate_with_websocket(self):
self.send_wsmessage()
while True:
recv_msg = yield self.wsconn.read_message()
if recv_msg is None:
self.wsconn.close()
break
yield tornado.gen.sleep(0.1)
self.send_wsmessage()
print("IoLoop terminate")
def main(self):
choice = input("Do you want to continue(y/n)? ")
if choice == "y" and self.has_initialised == False:
print("Yup")
self.init()
if self.has_initialised == True:
self.connect_on_websocket()
self.communicate_with_websocket()
if __name__ == "__main__":
try:
client = Client("http://localhost:8888/join", "ws://localhost:8888/game")
tornado.ioloop.IOLoop.instance().start()
except (SystemExit, KeyboardInterrupt):
print("Client closed")
通过阅读一些在线示例,我想出了上面的代码,但它不起作用。所以我的主要问题是
如何使 Tornado 协程与标准输入一起工作(命令行输入)
我的其他问题是:
(a) 我编写的代码是否适合使用 Tornado 协程?
(b) 如果不是,你能ELI5吗?我也很欣赏真正以有趣的方式(在任何中间级别)使用 Tornado 的代码示例,以便我可以从中学习。
(c) 在Python中有没有更直观的方法来做我想做的事?像 Flask+Gevents 或 Twisted 版本或可能更容易使用的纯套接字版本?
感谢您的帮助。
编辑:Flan 为我指出了一些错误,我修正了它,现在可以使用了。
正如我目前所见,问题不在于stdin 交互,而是您使用协程的错误方式。您的 connect_on_websocket
和 communicate_with_websocket
函数是协程,但您将它们用作普通函数,它不会起作用。我提出这些更改。
- 制作
main()
协程(添加装饰器),不要调用它,从 Client.__init__()
. 中移除
- 在 name=main block schedule
client.main()
调用 tornado.ioloop.IOLoop.instance().add_callback(client.main)
.
- 在
main
和 all 中,您的代码更改协程函数(使用 @tornado.gen.coroutine
)调用 yield,例如 yield self.connect_on_websocket()
只有 self.connect_on_websocket()
这应该足够了,您可以继续进行开发。
编辑后的代码是
服务器:
"""
Server module for game server
"""
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import uuid
import json
class Controller(object):
def __init__(self):
self.players = set()
self.connections = set()
def check(self):
return "Hi"
def run_somthing(self, text):
new_text = "Server: " + text
return new_text
class InitMessageHandler(tornado.web.RequestHandler):
def get(self):
print("Client has asked for initial details")
user_data = {}
user_data['user_id'] = str(uuid.uuid4())
self.write(json.dumps(user_data))
class GameHandler(tornado.websocket.WebSocketHandler):
def open(self):
# called anytime a new connection with this server is opened
print("Client connected")
print("Client sent: ", self)
if self not in self.application.controller.connections:
self.application.controller.connections.add(self)
def on_message(self, message):
# called anytime a new message is received
print("Received from client ,msg=", message)
msg = "Server: " + message
self.write_message(json.dumps(msg))
def check_origin(self, origin):
return True
def on_close(self):
# called a websocket connection is closed
if self in self.application.controller.connections:
self.application.controller.connections.remove(self)
class Server(tornado.web.Application):
def __init__(self):
self.controller = Controller()
handlers = [
(r"/join", InitMessageHandler),
(r"/game", GameHandler)
]
tornado.web.Application.__init__(self, handlers)
if __name__ == "__main__":
tornado.options.parse_command_line()
try:
application = Server()
server = tornado.httpserver.HTTPServer(application)
server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
print("Server closed")
客户
import tornado.ioloop
import tornado.websocket
import requests
import json
import sys
import tornado.gen
class Client(object):
def __init__(self, join_url, play_url):
self.wsconn = None
self.join_url = join_url
self.play_url = play_url
self.user_details = {}
def get_user_input(self, question=None):
str_choice = input(question)
while any((str_choice is None, not str_choice.strip())):
print("You entered an empty answer")
str_choice = input(question)
return str_choice
def _merge_dicts(*dict_args):
"""
Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
def generate_wsmessage(self):
msg_line = self.get_user_input("Enter message to send to server: ")
msg = {}
msg['message'] = msg_line
msg['user_id'] = self.user_details['user_id']
msg['user_name'] = self.user_details['user_name']
print("Message to send: ", msg)
return json.dumps(msg)
def init(self):
print("Heh")
username = self.get_user_input("What is your username? ")
print("Getting initial user details")
req = requests.get(self.join_url)
response = json.loads(req.text)
print(response)
self.user_details['user_name'] = username
self.user_details['user_id'] = response['user_id']
@tornado.gen.coroutine
def connect_on_websocket(self):
try:
self.wsconn = yield tornado.websocket.websocket_connect(self.play_url)
except Exception as e:
print("Connection error: {}".format(e))
else:
print("Server has connected to ")
yield self.send_wsmessage()
@tornado.gen.coroutine
def send_wsmessage(self):
msg = self.generate_wsmessage()
if not self.wsconn:
raise RuntimeError('Web socket connection is closed.')
yield self.wsconn.write_message(json.dumps(msg))
yield self.communicate_with_websocket()
@tornado.gen.coroutine
def communicate_with_websocket(self):
recv_msg = None
while True:
recv_msg = yield self.wsconn.read_message()
if recv_msg is None:
self.wsconn.close()
break
print("Server has replied with message=", recv_msg)
yield self.send_wsmessage()
print("IoLoop terminate")
@tornado.gen.coroutine
def main(self):
choice = input("Do you want to continue(y/n)? ")
if choice == "y":
print("Yup")
self.init()
yield self.connect_on_websocket()
if choice == "n":
sys.exit()
if __name__ == "__main__":
try:
client = Client("http://localhost:8888/join", "ws://localhost:8888/game")
tornado.ioloop.IOLoop.instance().add_callback(client.main)
tornado.ioloop.IOLoop.instance().start()
except (SystemExit, KeyboardInterrupt):
print("Client closed")
对于 (a)、(b),查看 here。对于 (c),另一次。
我正在尝试构建一个使用 Python 的多人游戏。我正在使用 Tornado 构建客户端和服务器。 理想情况下,我希望发生的事情如下:
(a) 客户端等待用户从命令行输入
(b) 当客户端获得用户输入时,将用户输入发送给服务器
(c) 服务器在其上模拟一些处理(这将是游戏引擎)并将响应发送回客户端。
服务器
"""
Server module for game server
"""
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import uuid
import json
class Controller(object):
def __init__(self):
self.players = ()
self.connections = ()
def check(self):
return "Hi"
def run_somthing(self, text):
new_text = "Server: " + text
return new_text
class InitMessageHandler(tornado.web.RequestHandler):
def get(self):
user_data = {}
user_data['user_id'] = str(uuid.uuid4())
self.write(json.dumps(user_data))
class GameHandler(tornado.websocket.WebSocketHandler):
def open(self):
# called anytime a new connection with this server is opened
print("Client connected")
print("Client sent: ", self)
if seif not in self.application.controller.connections:
self.application.controller.connections.add(self)
def on_message(self):
# called anytime a new message is received
pass
def check_origin(self, origin):
return True
def on_close(self):
# called a websocket connection is closed
if self in self.application.controller.connections:
self.application.controller.connections.remove(self)
class Server(tornado.web.Application):
def __init__(self):
self.controller = Controller()
handlers = [
(r"/join", InitMessageHandler),
(r"/game", GameHandler)
]
tornado.web.Application.__init__(self, handlers)
if __name__ == "__main__":
tornado.options.parse_command_line()
try:
application = Server()
server = tornado.httpserver.HTTPServer(application)
server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
print("closed")
客户
"""
Client module for the game clients(Players)
"""
import tornado.ioloop
import tornado.websocket
import requests
import json
import sys
import tornado.gen
class Client(object):
def __init__(self, join_url, play_url):
self.wsconn = None
self.join_url = join_url
self.play_url = play_url
#self.io_loop = tornado.ioloop.IOLoop.instance()
#self.io_loop.add_handler(sys.stdin, self.handle_user_input, tornado.ioloop.IOLoop.READ)
self.user_details = {}
self.has_initialised = False
#self.io_loop.start()
self.main()
def get_user_input(self, question=None):
str_choice = input(question)
while any((str_choice is None, not str_choice.strip())):
print("You entered an empty answer")
str_choice = input(question)
return str_choice
def _merge_dicts(*dict_args):
"""
Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
def generate_wsmessage(self):
msg_line = input("Enter message to send to server")
while any((msg_line is None, not msg_line.strip())):
print("You entered an empty answer")
msg_line = input("Enter message to send to server")
msg = {}
msg['message'] = msg_line
msg_to_send = self._merge_dicts(self.user_details, msg)
return json.dumps(msg_to-send)
def init(self):
print("Heh")
username = self.get_user_input("What is your username? ")
print("Getting initial user details")
req = requests.get(self.join_url)
response = json.loads(req.text)
print(response)
self.user_details['name'] = username
self.user_details['user_id'] = response['user_id']
self.has_initialised = True
def server_recv(self, msg):
print("Server has connected on websocket socket with msg=", msg)
@tornado.gen.coroutine
def connect_on_websocket(self):
try:
self.wsconn = yield tornado.websocket.websocket_connect(self.play_url, on_message_callback=self.server_recv)
except Exception as e:
print("Connection error: {}".format(e))
else:
print("Connected")
@tornado.gen.coroutine
def send_wsmessage(self):
msg = self.generate_wsmessage()
yield self.wsconn.write_message(msg)
@tornado.gen.coroutine
def communicate_with_websocket(self):
self.send_wsmessage()
while True:
recv_msg = yield self.wsconn.read_message()
if recv_msg is None:
self.wsconn.close()
break
yield tornado.gen.sleep(0.1)
self.send_wsmessage()
print("IoLoop terminate")
def main(self):
choice = input("Do you want to continue(y/n)? ")
if choice == "y" and self.has_initialised == False:
print("Yup")
self.init()
if self.has_initialised == True:
self.connect_on_websocket()
self.communicate_with_websocket()
if __name__ == "__main__":
try:
client = Client("http://localhost:8888/join", "ws://localhost:8888/game")
tornado.ioloop.IOLoop.instance().start()
except (SystemExit, KeyboardInterrupt):
print("Client closed")
通过阅读一些在线示例,我想出了上面的代码,但它不起作用。所以我的主要问题是
如何使 Tornado 协程与标准输入一起工作(命令行输入)
我的其他问题是:
(a) 我编写的代码是否适合使用 Tornado 协程?
(b) 如果不是,你能ELI5吗?我也很欣赏真正以有趣的方式(在任何中间级别)使用 Tornado 的代码示例,以便我可以从中学习。
(c) 在Python中有没有更直观的方法来做我想做的事?像 Flask+Gevents 或 Twisted 版本或可能更容易使用的纯套接字版本?
感谢您的帮助。
编辑:Flan 为我指出了一些错误,我修正了它,现在可以使用了。
正如我目前所见,问题不在于stdin 交互,而是您使用协程的错误方式。您的 connect_on_websocket
和 communicate_with_websocket
函数是协程,但您将它们用作普通函数,它不会起作用。我提出这些更改。
- 制作
main()
协程(添加装饰器),不要调用它,从Client.__init__()
. 中移除
- 在 name=main block schedule
client.main()
调用tornado.ioloop.IOLoop.instance().add_callback(client.main)
. - 在
main
和 all 中,您的代码更改协程函数(使用@tornado.gen.coroutine
)调用 yield,例如yield self.connect_on_websocket()
只有self.connect_on_websocket()
这应该足够了,您可以继续进行开发。
编辑后的代码是
服务器:
"""
Server module for game server
"""
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import uuid
import json
class Controller(object):
def __init__(self):
self.players = set()
self.connections = set()
def check(self):
return "Hi"
def run_somthing(self, text):
new_text = "Server: " + text
return new_text
class InitMessageHandler(tornado.web.RequestHandler):
def get(self):
print("Client has asked for initial details")
user_data = {}
user_data['user_id'] = str(uuid.uuid4())
self.write(json.dumps(user_data))
class GameHandler(tornado.websocket.WebSocketHandler):
def open(self):
# called anytime a new connection with this server is opened
print("Client connected")
print("Client sent: ", self)
if self not in self.application.controller.connections:
self.application.controller.connections.add(self)
def on_message(self, message):
# called anytime a new message is received
print("Received from client ,msg=", message)
msg = "Server: " + message
self.write_message(json.dumps(msg))
def check_origin(self, origin):
return True
def on_close(self):
# called a websocket connection is closed
if self in self.application.controller.connections:
self.application.controller.connections.remove(self)
class Server(tornado.web.Application):
def __init__(self):
self.controller = Controller()
handlers = [
(r"/join", InitMessageHandler),
(r"/game", GameHandler)
]
tornado.web.Application.__init__(self, handlers)
if __name__ == "__main__":
tornado.options.parse_command_line()
try:
application = Server()
server = tornado.httpserver.HTTPServer(application)
server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
print("Server closed")
客户
import tornado.ioloop
import tornado.websocket
import requests
import json
import sys
import tornado.gen
class Client(object):
def __init__(self, join_url, play_url):
self.wsconn = None
self.join_url = join_url
self.play_url = play_url
self.user_details = {}
def get_user_input(self, question=None):
str_choice = input(question)
while any((str_choice is None, not str_choice.strip())):
print("You entered an empty answer")
str_choice = input(question)
return str_choice
def _merge_dicts(*dict_args):
"""
Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
def generate_wsmessage(self):
msg_line = self.get_user_input("Enter message to send to server: ")
msg = {}
msg['message'] = msg_line
msg['user_id'] = self.user_details['user_id']
msg['user_name'] = self.user_details['user_name']
print("Message to send: ", msg)
return json.dumps(msg)
def init(self):
print("Heh")
username = self.get_user_input("What is your username? ")
print("Getting initial user details")
req = requests.get(self.join_url)
response = json.loads(req.text)
print(response)
self.user_details['user_name'] = username
self.user_details['user_id'] = response['user_id']
@tornado.gen.coroutine
def connect_on_websocket(self):
try:
self.wsconn = yield tornado.websocket.websocket_connect(self.play_url)
except Exception as e:
print("Connection error: {}".format(e))
else:
print("Server has connected to ")
yield self.send_wsmessage()
@tornado.gen.coroutine
def send_wsmessage(self):
msg = self.generate_wsmessage()
if not self.wsconn:
raise RuntimeError('Web socket connection is closed.')
yield self.wsconn.write_message(json.dumps(msg))
yield self.communicate_with_websocket()
@tornado.gen.coroutine
def communicate_with_websocket(self):
recv_msg = None
while True:
recv_msg = yield self.wsconn.read_message()
if recv_msg is None:
self.wsconn.close()
break
print("Server has replied with message=", recv_msg)
yield self.send_wsmessage()
print("IoLoop terminate")
@tornado.gen.coroutine
def main(self):
choice = input("Do you want to continue(y/n)? ")
if choice == "y":
print("Yup")
self.init()
yield self.connect_on_websocket()
if choice == "n":
sys.exit()
if __name__ == "__main__":
try:
client = Client("http://localhost:8888/join", "ws://localhost:8888/game")
tornado.ioloop.IOLoop.instance().add_callback(client.main)
tornado.ioloop.IOLoop.instance().start()
except (SystemExit, KeyboardInterrupt):
print("Client closed")
对于 (a)、(b),查看 here。对于 (c),另一次。