python - 带有标准输入的 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 版本,或者只是可能更容易使用的纯套接字版本?
谢谢你的帮助。
编辑:弗兰为我指出了一些错误,我修复了它,它现在可以工作了。
解决方案
正如我目前所看到的,问题不在于标准输入交互,而在于您使用协程的错误方式。您的connect_on_websocket
和communicate_with_websocket
函数是协程,但您将它们用作普通函数并且它不起作用。我提出这些改变。
- 制作
main()
协程(添加装饰器),不要调用它,从Client.__init__()
. - 在 name=main 块调度
client.main()
调用中使用tornado.ioloop.IOLoop.instance().add_callback(client.main)
. main
例如,在所有代码中更改协程函数(带有@tornado.gen.coroutine
)的调用以产生,yield self.connect_on_websocket()
而不是仅仅self.connect_on_websocket()
这应该足够了,因此您可以进一步进行开发。
推荐阅读
- php - 在 Guzzle POST 请求中使用 cookieJar 发送 cookie 不起作用
- android - 谷歌地图在安卓设备上找不到路线
- php - Codeigniter 获取两个日期的差值
- c++ - 对于 ~95% 写入/5% 读取线程安全无序映射,是否有一个简单的解决方案?
- jestjs - 用 Jest 处理 process.exit(1)
- css - 如何为不同的插槽设置不同的样式?
- internet-explorer-11 - Material-UI 下拉菜单在 IE11 中导致错误
- c - 一个程序,用于确定在 c 语言中创建字符串回文的最小插入次数。我得到的错误是缺少标准输出
- gradle - 无法解决最近创建的 grails 项目中的依赖关系
- c# - 使属性 getter 和 setter 相等(自动属性初始化器)