首页 > 解决方案 > 带有标准输入的 Tornado 客户端

问题描述

我正在尝试构建一个使用 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 版本,或者只是可能更容易使用的纯套接字版本?

谢谢你的帮助。

编辑:弗兰为我指出了一些错误,我修复了它,它现在可以工作了。

标签: pythonasynchronoustornadocoroutinegame-development

解决方案


正如我目前所看到的,问题不在于标准输入交互,而在于您使用协程的错误方式。您的connect_on_websocketcommunicate_with_websocket函数是协程,但您将它们用作普通函数并且它不起作用。我提出这些改变。

  1. 制作main()协程(添加装饰器),不要调用它,从Client.__init__().
  2. 在 name=main 块调度client.main()调用中使用tornado.ioloop.IOLoop.instance().add_callback(client.main).
  3. main例如,在所有代码中更改协程函数(带有@tornado.gen.coroutine)的调用以产生,yield self.connect_on_websocket()而不是仅仅self.connect_on_websocket()

这应该足够了,因此您可以进一步进行开发。


推荐阅读