简体   繁体   English

龙卷风客户端与标准输入

[英]Tornado client with stdin

I am trying to build a multiplayer game that uses Python. 我正在尝试构建使用Python的多人游戏。 I am using Tornado to build the client and server. 我正在使用Tornado构建客户端和服务器。 Ideally, what I would like to happen are as follows: 理想情况下,我希望发生的情况如下:

(a) For the client to wait for user input from the command line (a)让客户端等待命令行中的用户输入

(b) When the client gets user input, to send the user input to the server (b)客户端获得用户输入后,将用户输入发送到服务器

(c) for the server to simulate some processing(which will be the game engine) on it and send the response back to a client. (c)服务器模拟其上的某些处理(将是游戏引擎),并将响应发送回客户端。

The server 服务器

"""
    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") 

The client 客户端

"""
    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")

From reading the some examples online, I came up the code above, but it is not working. 通过在线阅读一些示例,我想到了上面的代码,但是没有用。 So my main question is 所以我的主要问题是

how to make Tornado coroutines work with stdin(command line input) 如何使用标准输入制作龙卷风协程(命令行输入)

My other questions are: 我的其他问题是:

(a) Is the code I have written the right way to work with Tornado coroutines or not? (a)我编写的代码是否正确使用了Tornado协程?

(b) If it is not, could you ELI5? (b)如果不是,您可以ELI5吗? Also I would appreciate code examples that really use Tornado in interesting ways(at any intermediate level) so that I can learn from them. 另外,我将欣赏以有趣的方式(在任何中间级别)真正使用Tornado的代码示例,以便我可以从中学习。

(c) Is there a more intuitive way to do what I want to do,in Python? (c)是否有更直观的方法可以在Python中完成我想做的事情? Like a Flask+Gevents or Twisted version or just pure sockets version that might be easier to work with? 像Flask + Gevents或Twisted版本,或者仅仅是更简单的纯套接字版本?

Thanks for your help. 谢谢你的帮助。

EDIT : Flan pointed out some errors for me and I fixed it and it works now. 编辑 :弗兰(Flan)为我指出了一些错误,我已将其修复,现在可以使用。

As I can see at the moment, problem is not in stdin interaction, but your wrong way of using coroutines. 正如我目前所看到的那样,问题不在stdin交互中,而是您使用协程的错误方式。 Your connect_on_websocket and communicate_with_websocket functions are coroutines but you are using them as a plain functions and it won't work. 您的connect_on_websocketcommunicate_with_websocket函数是协程,但是您将它们用作普通函数,将无法使用。 I propose these changes. 我建议这些更改。

  1. Make main() coroutine (add decorator), don't call it, remove from the Client.__init__() . 使main()协程(添加装饰器),不要调用它,从Client.__init__()删除。
  2. In name=main block schedule client.main() invocation with tornado.ioloop.IOLoop.instance().add_callback(client.main) . 在name = main块中,计划带有tornado.ioloop.IOLoop.instance().add_callback(client.main) client.main()调用。
  3. In main and all your code change coroutine-functions' (with @tornado.gen.coroutine ) calls to yield, for example yield self.connect_on_websocket() instead of just self.connect_on_websocket() main所有代码中,更改协程函数的调用(使用@tornado.gen.coroutine )以产生yield,例如yield self.connect_on_websocket()而不是self.connect_on_websocket()

That should be sufficient so you can proceed your development further. 这样就足够了,以便您可以进一步进行开发。

The edited code is 修改后的代码是

The server: 服务器:

"""
    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") 

The client 客户端

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")

For (a), (b) check out here . 对于(a),(b),请在此处签出。 For (c), another time. 对于(c),还有一次。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM