Skip to content

Commit b1e4346

Browse files
author
Francesco Mecatti
committed
Connected clients list and Context Manager
1 parent 27e8c75 commit b1e4346

File tree

2 files changed

+70
-43
lines changed

2 files changed

+70
-43
lines changed

OtherSocketExamples/asyncio_stream_simple_server.py

+58-27
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,27 @@
22
import logging
33
import threading
44
import asyncio
5-
import sys
65

76
"""
87
Connect to this server using netcat or similar utilities
98
"""
109

11-
HOST, PORT = "127.0.0.1", 9999
10+
HOST, PORT = "127.0.0.1", 9999 # Default values
1211

13-
# TODO: mantieni numero di client connessi e ritorna lista con indirizzi
14-
# TODO: trasforma write in writelines e termina con \n
15-
# TODO: usa readline e termina comando con \n
12+
CLIENTS = list()
1613

1714

1815
def handle_command(cmd: str, client_stream: asyncio.StreamWriter):
19-
if not cmd: return
16+
"""
17+
This function handle a command and returns the output (as a string) which has to be sent to the client
18+
19+
:param cmd: command to be handled
20+
:param client_stream: StreamWriter connected to the client that performs the request
21+
:return:
22+
:rtype: str
23+
"""
24+
25+
if not cmd: return # Connection terminated
2026
if cmd == '0':
2127
return f"Client address: {client_stream.get_extra_info('peername')}\n"
2228
elif cmd == '1':
@@ -27,64 +33,89 @@ def handle_command(cmd: str, client_stream: asyncio.StreamWriter):
2733
return f"Alive threads number: {threading.active_count()}\n"
2834
elif cmd == '4':
2935
return f"Alive threads: {[t.getName() for t in threading.enumerate()]}\n"
36+
elif cmd == '5':
37+
return f"Number of connected clients {len(CLIENTS)}\n"
38+
elif cmd == '6':
39+
return f"Connected clients: {CLIENTS}\n"
3040
else: # Help
3141
return """'0': Return the remote address (client) to which the socket is connected;
3242
'1': Return the server socket's own address;
3343
'2': Return the current thread name;
3444
'3': Return the number of alive threads;
35-
'4': Return the list of names of alive threads (comma separated);
45+
'4': Return the list of alive threads' names;
46+
'5': Return the number of connected clients;
47+
'6': Return the list of connected clients' names;
3648
'q': Quit server when all the clients will be disconnected;
3749
"""
3850

3951

40-
async def server_handler(reader, writer):
52+
async def close_connection(writer: asyncio.StreamWriter):
53+
global CLIENTS
54+
addr = writer.get_extra_info('peername')
55+
logging.warning(f"Closed connection: {addr}")
56+
CLIENTS.remove(writer.get_extra_info('peername'))
57+
writer.close()
58+
await writer.wait_closed()
59+
60+
61+
async def server_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
62+
global CLIENTS
4163
addr = writer.get_extra_info('peername')
4264
logging.info(f"Serving {addr}")
65+
CLIENTS.append(addr)
4366
while True:
4467
writer.write(">> ".encode())
45-
await writer.drain()
68+
await writer.drain() # Flushing data
4669
try:
47-
data = await reader.read(1024)
48-
except ConnectionResetError:
70+
data = await reader.readline()
71+
except ConnectionResetError: # Netcat for Windows sends RST packet to close the connection
72+
await close_connection(writer)
4973
logging.error(f"Closed connection - Reset Connection Error: {addr}")
5074
break
5175
cmd = data.decode().strip()
5276

5377
logging.info(f"Received {cmd} from {addr}")
5478

5579
if not cmd:
56-
writer.close()
80+
await close_connection(writer)
5781
break
5882
elif cmd == 'q':
59-
logging.warning(f"Closed connection: {addr}")
60-
writer.close()
83+
await close_connection(writer)
6184
break
6285
else:
63-
writer.write(handle_command(cmd, writer).encode()) # TODO: writeline
86+
writer.write(handle_command(cmd, writer).encode())
6487
await writer.drain()
6588

6689

67-
async def main():
68-
global HOST, PORT
69-
logging.basicConfig(level=logging.DEBUG, format="%(threadName)s --> %(asctime)s - %(levelname)s: %(message)s",
70-
datefmt="%H:%M:%S")
71-
parser = argparse.ArgumentParser()
72-
parser.add_argument("-a", "--address", help="host address", default=HOST, type=str, dest="address")
73-
parser.add_argument("-p", "--port", help="port number", default=PORT, type=int, dest="port")
74-
args = parser.parse_args() # TODO: non funzionante con asyncio
75-
PORT = args.port
76-
HOST = args.address
90+
async def keyboard_listener():
91+
while True:
92+
await asyncio.sleep(1)
7793

78-
logging.info(f"Server running on {HOST}:{PORT}...")
7994

95+
async def main(host, port):
8096
server = await asyncio.start_server(server_handler, HOST, PORT)
8197

98+
logging.info(f"Server running on {host}:{port}...")
99+
100+
keyboard_listener_task = asyncio.create_task(keyboard_listener())
101+
await keyboard_listener_task # Concurrent task
102+
82103
async with server:
83104
await server.serve_forever()
84105

85106

86107
if __name__ == "__main__":
108+
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s: %(message)s",
109+
datefmt="%H:%M:%S")
110+
parser = argparse.ArgumentParser()
111+
parser.add_argument("-a", "--address", help="host address", default=HOST, type=str,
112+
dest="address") # If an argument is not provided the default value is used
113+
parser.add_argument("-p", "--port", help="port number", default=PORT, type=int, dest="port")
114+
args = parser.parse_args()
115+
PORT = args.port
116+
HOST = args.address
117+
87118
try:
88-
asyncio.run(main())
119+
asyncio.run(main(HOST, PORT))
89120
except KeyboardInterrupt:
90121
logging.warning("Server stopping...")

OtherSocketExamples/socketserver_simple_server.py

+12-16
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99

1010
HOST, PORT = "127.0.0.1", 9999
1111

12+
1213
# TODO: termina con \n --> adatta ricezione e trasmissione
1314

14-
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
15+
class RequestHandler(socketserver.BaseRequestHandler):
1516

1617
def handle(self):
1718
logging.info(f"New connection {self.client_address}".encode())
@@ -22,7 +23,7 @@ def handle(self):
2223
except ConnectionResetError:
2324
logging.error(f"Closed connection - Reset Connection Error: {self.client_address}")
2425
break
25-
cmd = data[0]
26+
cmd = data
2627
logging.debug(f"Received: {cmd}")
2728
if not cmd: break
2829
if cmd == '0':
@@ -50,12 +51,14 @@ def handle(self):
5051
logging.info(f"Connection closed by client {self.client_address}")
5152

5253

53-
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
54-
pass
54+
def main(host, port):
55+
logging.info(f"Server running on {host}:{port}...")
56+
57+
with socketserver.ThreadingTCPServer((host, port), RequestHandler) as server:
58+
server.serve_forever()
5559

5660

57-
def main():
58-
global HOST, PORT
61+
if __name__ == "__main__":
5962
logging.basicConfig(level=logging.DEBUG, format="%(threadName)s --> %(asctime)s - %(levelname)s: %(message)s",
6063
datefmt="%H:%M:%S")
6164
parser = argparse.ArgumentParser()
@@ -64,15 +67,8 @@ def main():
6467
args = parser.parse_args()
6568
PORT = args.port
6669
HOST = args.address
67-
logging.info(f"Server running on {HOST}:{PORT}...")
68-
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
70+
6971
try:
70-
server.serve_forever()
72+
main(HOST, PORT)
7173
except KeyboardInterrupt:
72-
logging.warning("Stopping...")
73-
finally:
74-
server.server_close()
75-
76-
77-
if __name__ == "__main__":
78-
main()
74+
logging.warning("Stopping server...")

0 commit comments

Comments
 (0)