So this should be fairly simple, i have a client that connects to a server, and it can receive messages from the server just fine. However i need to be able to send messages to the server. I am using asyncio to handle these asynchronously but i have a problem with that. How do i get the user input to my client so it can use its transport to send the data up to the server. Heres the code.
import asyncio, zen_utils, json
from struct import *
class ChatClient(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.address = transport.get_extra_info('peername')
self.data = b''
print('Accepted connection from {}'.format(self.address))
self.username = b'jacksonm'
json_name = b'{"USERNAME": "'+self.username+b'"}'
length = len(json_name)
code_len = pack(b'!I', length)
message = code_len + json_name
self.json_loaded = False
self.next_length = -1
self.transport.write(message)
def data_received(self, data):
self.data += data
if (self.json_loaded == False):
self.compile_server_data(data)
elif(self.json_loaded):
if (len(self.data) > 4 and self.next_length == -1):
self.next_length = self.data[:4]
self.next_length = unpack(b'!I', self.next_length)[0]
print("LENGTH: ", self.next_length)
elif (len(self.data) >= self.next_length):
self.data = self.data[4:]
print("MESSAGE: ", self.data)
self.next_length = -1
def compile_server_data(self, data):
if (self.data.find(b'}') != -1):
start_index = self.data.find(b'{')
end_index = self.data.find(b'}')
self.json_data = self.data[start_index:end_index + 1]
self.data = self.data[end_index + 1:]
self.json_data = self.json_data.decode('ascii')
self.json_data = json.loads(self.json_data)
self.json_loaded = True
self.print_server_status()
def send_message(self, message):
message = message.encode('ascii')
length = len(message)
code_len = pack(b'!I', length)
message = code_len + message
def parse_message(self, raw_message):
message = {}
message['SRC'] = self.username
message['DEST'] = b'ALL'
message['TIMESTAMP'] = int(time.time())
message['CONTENT'] = b'test_message'
json_message = json.loads(message)
print (json_message)
def print_server_status(self):
print ("USERS:")
for user in self.json_data["USER_LIST"]:
print(user)
print()
print("MESSAGES:")
for message in self.json_data["MESSAGES"][-10:]:
print ("From: ", message[0], " ", "To: ", message[1])
print ("Message: ", message[3])
print()
def get_inital_data(self):
pass
def connection_lost(self, exc):
if exc:
print('Client {} error: {}'.format(self.address, exc))
elif self.data:
print('Client {} sent {} but then closed'
.format(self.address, self.data))
else:
print('Client {} closed socket'.format(self.address))
#asyncio.coroutine
def handle_user_input(loop):
"""reads from stdin in separate thread
if user inputs 'quit' stops the event loop
otherwise just echos user input
"""
while True:
message = yield from loop.run_in_executor(None, input, "> ")
if message == "quit":
loop.stop()
return
print(message)
if __name__ == '__main__':
address = zen_utils.parse_command_line('asyncio server using callbacks')
loop = asyncio.get_event_loop()
coro = loop.create_connection(ChatClient, *address)
server = loop.run_until_complete(coro)
# Start a task which reads from standard input
asyncio.async(handle_user_input(loop))
print('Listening at {}'.format(address))
try:
loop.run_forever()
finally:
server.close()
loop.close()
This is working in my tests by changing one line, using Python 3.4:
Change the run_until_complete() call to receive a (transport,protocol) pair instead of a single value.
Specifically, change server = loop.run_until_complete(coro) to transport, protocol = loop.run_until_complete(coro), because the call executes the coroutine and returns its value which is a pair of items. The first item is the server object, and the second entry is a protocol object which is an instance of ChatClient.
Run the server then the client in different command windows.
Client code:
import asyncio, json #, zen_utils
from struct import *
class ChatClient(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.address = transport.get_extra_info('peername')
self.data = b''
print('Accepted connection from {}'.format(self.address))
def data_received(self, data):
pass
def compile_server_data(self, data):
pass
def send_message(self, message):
message = message.encode('ascii')
self.transport.write(message)
def parse_message(self, raw_message):
pass
def print_server_status(self):
pass
def get_inital_data(self):
pass
def connection_lost(self, exc):
if exc:
print('Client {} error: {}'.format(self.address, exc))
elif self.data:
print('Client {} sent {} but then closed'
.format(self.address, self.data))
else:
print('Client {} closed socket'.format(self.address))
#asyncio.coroutine
def handle_user_input(loop, protocol):
"""reads from stdin in separate thread
if user inputs 'quit' stops the event loop
otherwise just echos user input
"""
while True:
message = yield from loop.run_in_executor(None, input, "> ")
if message == "quit":
loop.stop()
return
print(message)
protocol.send_message(message)
if __name__ == '__main__':
address = "127.0.0.1"
port = 82
loop = asyncio.get_event_loop()
coro = loop.create_connection(ChatClient, address, port) #*address
transport, protocol = loop.run_until_complete(coro)
# Start a task which reads from standard input
asyncio.async(handle_user_input(loop,protocol))
print('Listening at {}'.format(address))
try:
loop.run_forever()
finally:
transport.close()
loop.close()
Server code, credit goes to this site:
import socket
host = '' # Symbolic name meaning all available interfaces
port = 82 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, port))
s.listen(1)
conn, addr = s.accept()
print('Connected by', addr)
while True:
data = conn.recv(1024)
if not data: break
print( 'Received ', repr(data) )
if repr(data)=="stop": break
conn.sendall(data)
conn.close()
Related
I am using the following example code to connect Binancr Websocket. It all of sudden stopped and halted the program and print None on the screen:
PAIR = 'ethusdt'
WS_ENDPOINT = 'wss://fstream.binance.com/ws/{}#trade'.format(PAIR.lower())
def handle_trades(json_message):
pass
def ws_trades():
def on_ping(wsapp, message):
print("Got a ping! A pong reply has already been automatically sent.")
def on_pong(wsapp, message):
print("Got a pong! No need to respond")
def on_open(wsapp):
print("Initiating Connection")
def on_close(wsapp, close_status_code, close_msg):
print('close_status_code = ', close_status_code)
print('close_msg = ', close_msg)
def on_message(wsapp, message):
# current_time = datetime.datetime.today()
# print(current_time)
json_message = json.loads(message)
handle_trades(json_message)
def on_error(wsapp, error):
print('Error during Websocket connection')
print(error)
wsapp = websocket.WebSocketApp(WS_ENDPOINT, on_message=on_message, on_error=on_error)
wsapp.run_forever()
if __name__ == '__main__':
ws_trades()
How do I learn what is the actual error and how come I keep it running even if it halts for a while due to absence of incoming messages?
Below is the image of the screen.
I am coding an online terminal game and when I print for my recieve message thread it causes the other thread to pause. I am fairly new to sockets and threading so some code may be written poorly.
My main client thread
class Client:
def __init__(self, serverobj):
self.s = serverobj
self.gameLoop()
def gameLoop(self):
while True:
if event == None:
cmd = input(">")
parse(cmd, self.s)
else:
print(event)
res = input()
Networking Class
class ServerCon:
def __init__(self,ip,port):
self.ip = ip
self.port = port
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def joinServer(self):
self.client.connect((self.ip, self.port))
self.recvThread = threading.Thread(target=self.recvMSG)
self.recvThread.start()
def sendMSG(self, msg):
message = msg.encode(FORMAT)
msg_length = len(message)
send_length = str(msg_length).encode(FORMAT)
send_length += b' ' * (HEADER - len(send_length))
self.client.sendall(send_length)
self.client.sendall(message)
if self.client.recv(2048).decode(FORMAT):
return
else:
print("Error sending MSG")
def recvMSG(self):
while True:
data = self.client.recv(2048).decode(FORMAT)
if not data:
sys.exit()
print(data)
There is some code not included for parsing commands and starting the application but that is the basis. When the recv thread receives a message it will print it but the input line(>) will not show up and the application will stall.
Is there a way to memorize all the client instances (in a dictionary for example), and send a message/communicate on demand to whichever client needed?
import asyncio
class EchoServerClientProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.peername = transport.get_extra_info('peername')
print('Connection from {}'.format(self.peername))
def data_received(self, data):
message = data.decode()
print('Data received: {!r}'.format(message))
print('Send: {!r}'.format(message))
self.transport.write(data)
def connection_lost(self, exc):
print('Lost connection of {}'.format(self.peername))
self.transport.close()
loop = asyncio.get_event_loop()
coro = loop.create_server(EchoServerClientProtocol, '127.0.0.1', 8888)
server = loop.run_until_complete(coro)
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
If I understand your question correctly, you want to be able to maintain a map of connections and then send data to the connections at a later time.
import asyncio
from functools import partial
from random import choice, randint
class Echo(asyncio.Protocol):
def __init__(self, client_map):
self.client_map = client_map
def connection_made(self, transport):
self.transport = transport
peername = transport.get_extra_info("peername")
print(f"[x] connected to {peername}")
self.client_map[transport.get_extra_info("peername")] = self
def connection_lost(self, exc):
peername = self.transport.get_extra_info("peername")
print(f"[!] disconnected from {peername}")
del self.client_map[peername]
def data_received(self, data):
print(f"<<< {data}")
self.transport.write(b">>> " + data)
def protocol_factory(client_map):
proto = Echo(client_map)
return proto
def random_hello(loop, client_map):
delay = randint(1, 10)
if len(client_map) > 0:
peername = choice(sorted(client_map))
client_map[peername].transport.write(b">>> random hello\n")
loop.call_later(
delay, random_hello, loop, client_map,
)
async def main():
loop = asyncio.get_running_loop()
client_map = {}
_protocol_factory = partial(protocol_factory, client_map=client_map)
server = await loop.create_server(_protocol_factory, "127.0.0.1", 8888,)
random_hello(loop, client_map)
async with server:
await server.serve_forever()
asyncio.run(main())
What makes this all work is protocol_factory(). You pass in a client_map: dict, which will then be a shared variable in all the protocol objects, and as clients connect, the protocol object will be stored under the peername key. The random_hello() function demonstrates sending data to a protocol. Pass in client_map anywhere you want to be able to access connected clients and send data over the network.
I am trying to build a multiplayer game that uses Python. I am using Tornado to build the client and server. Ideally, what I would like to happen are as follows:
(a) For the client to wait for user input from the command line
(b) When the client gets user input, to send the user input to the server
(c) for the server to simulate some processing(which will be the game engine) on it and send the response back to a client.
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?
(b) If it is not, could you 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.
(c) Is there a more intuitive way to do what I want to do,in Python? Like a Flask+Gevents or Twisted version or just pure sockets version that might be easier to work with?
Thanks for your help.
EDIT: Flan pointed out some errors for me and I fixed it and it works now.
As I can see at the moment, problem is not in stdin interaction, but your wrong way of using coroutines. 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. I propose these changes.
Make main() coroutine (add decorator), don't call it, remove from the Client.__init__().
In name=main block schedule client.main() invocation with tornado.ioloop.IOLoop.instance().add_callback(client.main).
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()
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. For (c), another time.
i dont understand why my code returns that ERROR,
i have return in log_in and purpose but it doesnt accept that... why?
i have tried to put the list in an argument a or just
returns a string argument but it doesnt change anything...
Traceback (most recent call last):
File "D:/HIGHTS/Documents/Projects/ChatProject/example.py", line 149, in <module>
main()
File "D:/HIGHTS/Documents/Projects/ChatProject/example.py", line 139, in main
account_name = lis[0]
TypeError: 'NoneType' object has no attribute '__getitem__'
server code:
class Server():
def __init__(self, host, port):
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.clients = 0
self.clients_dict = {}
self.sock.bind((self.host, self.port))
self.sock.listen(100)
print 'listening...'
def close(self): # the function that responsible of closing the connection between the server and the clients
self.sock.close()
class ClientHandler():
def __init__(self, connection1, connection2):
super(ClientHandler, self).__init__()
self.connection1 = connection1
self.connection2 = connection2
def con1(self):
while True:
data = self.connection1.recv(1024)
self.connection2.send(data)
def con2(self):
while True:
data = self.connection2.recv(1024)
self.connection1.send(data)
def run(self):
t = threading.Thread(target=self.con1)
tt = threading.Thread(target=self.con2)
while True:
t.start()
tt.start()
t.join()
tt.join()
def account_check(name, password, dictionary):
if dictionary[name][0] == password:
return True
else:
return False
def log_in(s, connection, address):
connection.send("Name: ")
account_name = connection.recv(1024)
connection.send("Password: ")
account_password = connection.recv(1024)
if account_name in s.clients_dict:
while not account_check(account_name, account_password, s.clients_dict):
connection.send('False')
connection.send("Name: ")
account_name = connection.recv(1024)
connection.send("Password: ")
account_password = connection.recv(1024)
connection.send('True')
s.clients_dict[account_name][1] = address
s.clients_dict[account_name][3] = 'connected'
else:
connection.send('True')
s.clients_dict[account_name] = [account_password, address, connection, 'connected']
s.clients += 1
a = [account_name, s.clients_dict, s.clients]
print 'dsf ad'
return a
def purpose(connection, dictionary, account_name):
chatter_name = connection.recv(1024)
if chatter_name in dictionary:
if dictionary[chatter_name][3] == 'connected':
client_handler = ClientHandler(dictionary[chatter_name][2], dictionary[account_name][2],)
client_handler.start()
else:
logging.debug("not connected")
else:
logging.debug("there is no such user")
return dictionary
def main():
s = Server('0.0.0.0', 555)
while True and s.clients < 101:
connection, address = s.sock.accept()
t = threading.Thread(target=log_in, args=(s,connection, address,))
t.start()
lis = t.join()
account_name = lis[0]
s.clients_dict = lis[1]
s.clients = lis[2]
tt = threading.Thread(target=purpose, args=(connection, s.clients_dict, account_name,))
tt.start()
s.clients_dict = tt.join()
client code:
class Client(object):
def __init__(self, ip, port):
self.ip = ip
self.port = port
self.sock = socket.socket()
def connection(self): # a function that responsible to connect to the server
self.sock.connect((self.ip, self.port))
print self.sock.recv(1024)
self.sock.send(raw_input())
print self.sock.recv(1024)
self.sock.send(raw_input())
ok = self.sock.recv(1024)
if ok == 'True':
ok = True
else:
ok = False
while not ok:
print self.sock.recv(1024)
self.sock.send(raw_input())
print self.sock.recv(1024)
self.sock.send(raw_input())
ok = self.sock.recv(1024)
if ok == 'True':
ok = True
else:
ok = False
print "connected successfully"
x = raw_input("do you want to open a private chat? Y for yes or N for no")
if x == 'Y':
self.private(raw_input("with who?"))
def private(self, client):
self.sock.send(client)
def send_message(self): # a function that responsible to send a message to the server
while True:
self.sock.send(raw_input())
def recieving_message(self): # a function that responsible to receive a message from the server
while True:
data = self.sock.recv(1024)
print data
def main():
c = Client('127.0.0.1', 555)
c.connection() # connect to the server
t1 = threading.Thread(target=c.send_message) # a thread of sending a message
t1.start()
t2 = threading.Thread(target=c.recieving_message) # a thread of recieving a message
t2.start()