Edited my question to provide more information and a reproducible example.
I'm not sure if anybody could help me here. But I'm having some problems with my asyncio socket server.
As you can see, in the server.py on line 18 there is a deliberate line that will cause a value error, as you cannot change "" to an int. This is intentional, as for some reason the error on this line is never output to the console.
When the client connects to the server the print("here1") line runs, but the lines after that do not due to the error on line 18. I need this error to be output into console, but it appears nowhere. I am very confused and cannot find anything online about why asyncio could be eating the errors and not displaying them.
I have attempted to see any errors by using the logging module, but even this doesn't show any errors..
import logging
logging.basicConfig(level=logging.ERROR)
The line which causes the error is definitely running.
I have extracted all of my code into a smaller reproducible set of files below.
server.py
import asyncio
class Server:
def __init__(self, host: str, port: int):
self.host = host
self.port = port
async def start_server(self):
print("Server online")
server = await asyncio.start_server(self.handle_events, self.host, self.port)
async with server:
await server.serve_forever()
async def handle_events(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
while True:
print("here1")
int("")
print("here2")
data = await reader.read(1024)
if not data:
continue
print(f"received: {data}")
async def start():
host = "127.0.0.1"
port = 55551
server = Server(host=host, port=port)
server_task = asyncio.create_task(server.start_server())
await asyncio.gather(server_task)
if __name__ == "__main__":
asyncio.run(start())
client.py
import asyncio
class Client:
def __init__(self, host: str, port: int):
super().__init__()
self.host = host
self.port = port
self.writer = None
self.reader = None
async def start_connection(self):
self.reader, self.writer = await asyncio.open_connection(host=self.host, port=self.port)
await self.message_handler()
async def message_handler(self):
while True:
await asyncio.sleep(0)
data = await self.reader.read(1024)
if not data:
continue
await self.writer.drain()
async def start():
host = "127.0.0.1"
port = 55551
server = Client(host=host, port=port)
server_task = asyncio.create_task(server.start_connection())
await asyncio.gather(server_task)
if __name__ == "__main__":
asyncio.run(start())
Cause it's not even calling that handle_events func.
import asyncio
async def tcp_echo_client(message):
reader, writer = await asyncio.open_connection(
'127.0.0.1', 55555)
print(f'Send: {message!r}')
writer.write(message.encode())
await writer.drain()
data = await reader.read(100)
print(f'Received: {data.decode()!r}')
print('Close the connection')
writer.close()
asyncio.run(tcp_echo_client('Hello World!'))
I just copied this from the doc so I can test it.
Related
I've tried to setup a Python server that allows clients to connect and then wait for commands from the server to then run those commands and return a response. I'm also trying to allow multiple clients to connect to one server. This is the code so far:
server.py
import asyncio
import threading
import datetime
import time
async def client_connected(reader, writer):
data = await reader.read(100)
message = (data.decode())
addr = writer.get_extra_info('peername')
print(f"Received {message!r} from {addr!r}")
print(f"Send: {message!r}")
writer.write(message.encode())
await writer.drain()
async def main():
server = await asyncio.start_server(
client_connected, '127.0.0.1', 8888)
addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets)
print(f'Serving on {addrs}')
async with server:
await server.serve_forever()
def msg(message):
for user in list_of_users:
print('send message - ', message)
user.write(message.encode())
asyncio.run(main())
client.pi
import asyncio
import subprocess
async def tcp_echo_client(message):
reader, writer = await asyncio.open_connection(
'127.0.0.1', 8888)
print(f'Send: {message!r}')
writer.write(message.encode())
data = await reader.read(100)
print(f'Received: {data.decode()!r}')
while True:
print("[-] Awaiting commands...")
command = await reader.read(1024)
command = command.decode()
op = subprocess.Popen(command, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
output = op.stdout.read()
output_error = op.stderr.read()
print("[-] Sending response...")
output_msg = output + output_error
writer.write(output_msg.encode)
asyncio.run(tcp_echo_client('Hello World!'))
What I don't understand is:
If I'm handling multiple client connections correctly
How to allow for commands to be input on the server and then sent our properly to each client
If I need a separate "main" client that sends out the commands or if I can run a terminal window directly using the server itself.
Any advice would be much appreciated!
I try to build a base http server with the following code.
async def handle_client(client, address):
print('connection start')
data = await loop.sock_recv(client, 1024)
resp = b'HTTP/1.1 404 NOT FOUND\r\n\r\n<h1>404 NOT FOUND</h1>'
await loop.sock_sendall(client, resp)
client.close()
async def run_server():
while True:
client, address = await loop.sock_accept(server)
print('start')
loop.create_task(handle_client(client,address))
print(client)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 3006))
server.listen(8)
print(1)
loop = asyncio.get_event_loop()
loop.run_until_complete(run_server())
The output I expect to get is
1
start
connection start
But the actual result of running is
1
start
start
start
It seems that the function in loop.create_task() is not being run, so now I got confuesed., what is the correct way to use loop.create_task()?
You need to await the task that is created via loop.create_task(), otherwise run_server() will schedule the task and then just exit before the result has been returned.
Try changing run_server() to the following:
async def run_server():
while True:
client, address = await loop.sock_accept(server)
print('start')
await loop.create_task(handle_client(client,address))
print(client)
I am trying to make a socket server that's able to have multiple clients connected using the asyncio sockets and is able to easily switch between which client it communicates while still having all the clients connected. I thought there would be some type of FD of the clients like there is in sockets, but I looked through the docs and did not find anything, or I missed it.
Here is my server code:
import socket
import asyncio
host = "localhost"
port = 9998
list_of_auths = ['desktop-llpeu0p\\tomiss', 'desktop-llpeu0p\\tomisss',
'desktop-llpeu0p\\tomissss', 'desktop-llpeu0p\\tomisssss']
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('socket initiated.')
confirmed = 'CONFIRMED'
deny = 'denied'
#(so i dont forget) to get recv in async do: var = (await reader.read(4096)).decode('utf-8') if -1 then it will read all
#(so i dont forget) to write sendall do: writer.write(var.encode('utf-8')) should be used with await writer.drain()
async def handle_client(reader, writer):
idrecv = (await reader.read(255)).decode('utf-8')
if idrecv in list_of_auths:
writer.write(confirmed.encode('utf-8'))
else:
writer.write(deny.encode('utf-8'))
writer.close()
request = None
while request != 'quit':
print("second checkpoint")
writer.close()
async def run_server():
print("first checkpoint")
server = await asyncio.start_server(handle_client, host, port)
async with server:
await server.serve_forever()
asyncio.run(run_server())
This code allows multiple clients to connect at once; However, it only lets me communicate with the last one that connected.
I would suggest to implement it like so:
class SocketHandler(asyncio.Protocol):
def __init__(self):
asyncio.Protocol.__init__(self)
self.transport = None
self.peername = None
# your other code
def connection_made(self, transport):
""" incoming connection """
global ALL_CONNECTIONS
self.transport = transport
self.peername = transport.get_extra_info('peername')
ALL_CONNECTIONS.append(self)
# your other code
def connection_lost(self, exception):
self.close()
# your other code
def data_received(self, data):
# your code handling incoming data
def close(self):
try:
self.transport.close()
except AttributeError:
pass
# global list to store all connections
ALL_CONNECTIONS = []
def send_to_all(message):
""" sending a message to all connected clients """
global ALL_CONNECTIONS
for sh in ALL_CONNECTIONS:
# here you can also check sh.peername to know which client it is
if sh.transport is not None:
sh.transport.write(message)
port = 5060
loop = asyncio.get_event_loop()
coro = loop.create_server(SocketHandler, '', port)
server = loop.run_until_complete(coro)
loop.run_forever()
This way, each connection to the server is represented by an instance of SocketHandler. Whenever you process some data inside this instance, you know which client connection it is.
I would like to re-implement my code using asyncio coroutines instead of multi-threading.
server.py
def handle_client(client):
request = None
while request != 'quit':
request = client.recv(255).decode('utf8')
response = cmd.run(request)
client.send(response.encode('utf8'))
client.close()
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 15555))
server.listen(8)
try:
while True:
client, _ = server.accept()
threading.Thread(target=handle_client, args=(client,)).start()
except KeyboardInterrupt:
server.close()
client.py
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect(('localhost', 15555))
request = None
try:
while request != 'quit':
request = input('>> ')
if request:
server.send(request.encode('utf8'))
response = server.recv(255).decode('utf8')
print(response)
except KeyboardInterrupt:
server.close()
I know there are some appropriate asynchronous network librairies to do that. But I just want to only use asyncio core library on this case in order to have a better understanding of it.
It would have been so nice to only add async keyword before handle client definition... Here a piece of code which seems to work, but I'm still confused about the implementation.
asyncio_server.py
def handle_client(client):
request = None
while request != 'quit':
request = client.recv(255).decode('utf8')
response = cmd.run(request)
client.send(response.encode('utf8'))
client.close()
def run_server(server):
client, _ = server.accept()
handle_client(client)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 15555))
server.listen(8)
loop = asyncio.get_event_loop()
asyncio.async(run_server(server))
try:
loop.run_forever()
except KeyboardInterrupt:
server.close()
How adapt this in the best way and using async await keywords.
The closest literal translation of the threading code would create the socket as before, make it non-blocking, and use asyncio low-level socket operations to implement the server. Here is an example, sticking to the more relevant server part (the client is single-threaded and likely fine as-is):
import asyncio, socket
async def handle_client(client):
loop = asyncio.get_event_loop()
request = None
while request != 'quit':
request = (await loop.sock_recv(client, 255)).decode('utf8')
response = str(eval(request)) + '\n'
await loop.sock_sendall(client, response.encode('utf8'))
client.close()
async def run_server():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 15555))
server.listen(8)
server.setblocking(False)
loop = asyncio.get_event_loop()
while True:
client, _ = await loop.sock_accept(server)
loop.create_task(handle_client(client))
asyncio.run(run_server())
The above works, but is not the intended way to use asyncio. It is very low-level and therefore error-prone, requiring you to remember to set the appropriate flags on the socket. Also, there is no buffering, so something as simple as reading a line from the client becomes a tiresome chore. This API level is really only intended for implementors of alternative event loops, which would provide their implementation of sock_recv, sock_sendall, etc.
Asyncio's public API provides two abstraction layers intended for consumption: the older transport/protocol layer modeled after Twisted, and the newer streams layer. In new code, you almost certainly want to use the streams API, i.e. call asyncio.start_server and avoid raw sockets. That significantly reduces the line count:
import asyncio, socket
async def handle_client(reader, writer):
request = None
while request != 'quit':
request = (await reader.read(255)).decode('utf8')
response = str(eval(request)) + '\n'
writer.write(response.encode('utf8'))
await writer.drain()
writer.close()
async def run_server():
server = await asyncio.start_server(handle_client, 'localhost', 15555)
async with server:
await server.serve_forever()
asyncio.run(run_server())
I have read the answers and comments above, trying to figure out how to use the asyncio lib for sockets.
As it often happens with Python, the official documentation along with the examples is the best source of useful information.
I got understanding of Transports and Protocols (low-level API), and Streams (high-level API) from the examples presented in the end of the support article.
For example, TCP Echo Server:
import asyncio
class EchoServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
peername = transport.get_extra_info('peername')
print('Connection from {}'.format(peername))
self.transport = transport
def data_received(self, data):
message = data.decode()
print('Data received: {!r}'.format(message))
print('Send: {!r}'.format(message))
self.transport.write(data)
print('Close the client socket')
self.transport.close()
async def main():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
server = await loop.create_server(
lambda: EchoServerProtocol(),
'127.0.0.1', 8888)
async with server:
await server.serve_forever()
asyncio.run(main())
and TCP Echo Client:
import asyncio
class EchoClientProtocol(asyncio.Protocol):
def __init__(self, message, on_con_lost):
self.message = message
self.on_con_lost = on_con_lost
def connection_made(self, transport):
transport.write(self.message.encode())
print('Data sent: {!r}'.format(self.message))
def data_received(self, data):
print('Data received: {!r}'.format(data.decode()))
def connection_lost(self, exc):
print('The server closed the connection')
self.on_con_lost.set_result(True)
async def main():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()
on_con_lost = loop.create_future()
message = 'Hello World!'
transport, protocol = await loop.create_connection(
lambda: EchoClientProtocol(message, on_con_lost),
'127.0.0.1', 8888)
# Wait until the protocol signals that the connection
# is lost and close the transport.
try:
await on_con_lost
finally:
transport.close()
asyncio.run(main())
Hope it help someone searching for simple explanation of asyncio.
Is there a standard pattern for using asyncio to poll intermittent servers?
Take the following example: a client that reads every second from an unreliable TCP server. Without error handling, the async/await notation looks great:
class BasicTCPClient(object):
"""TCP client without error handling."""
def __init__(self, ip, port):
self.ip = ip
self.port = port
async def connect(self):
reader, writer = await asyncio.open_connection(self.ip, self.port)
self.connection = {'reader': reader, 'writer': writer}
async def get(self, request):
self.connection['writer'].write(request.encode())
return await self.connection['reader'].readuntil(b'\n')
async def read():
client = BasicTCPClient('ip', 8000)
await client.connect()
while True:
print(await client.get('request'))
await asyncio.sleep(1)
ioloop = asyncio.get_event_loop()
try:
ioloop.run_until_complete(read())
except KeyboardInterrupt:
pass
But this doesn't handle disconnecting/reconnecting well. I have a working solution, but it's approaching 100 LOC and negates all the elegance of async/await.