2
2
import logging
3
3
import threading
4
4
import asyncio
5
- import sys
6
5
7
6
"""
8
7
Connect to this server using netcat or similar utilities
9
8
"""
10
9
11
- HOST , PORT = "127.0.0.1" , 9999
10
+ HOST , PORT = "127.0.0.1" , 9999 # Default values
12
11
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 ()
16
13
17
14
18
15
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
20
26
if cmd == '0' :
21
27
return f"Client address: { client_stream .get_extra_info ('peername' )} \n "
22
28
elif cmd == '1' :
@@ -27,64 +33,89 @@ def handle_command(cmd: str, client_stream: asyncio.StreamWriter):
27
33
return f"Alive threads number: { threading .active_count ()} \n "
28
34
elif cmd == '4' :
29
35
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 "
30
40
else : # Help
31
41
return """'0': Return the remote address (client) to which the socket is connected;
32
42
'1': Return the server socket's own address;
33
43
'2': Return the current thread name;
34
44
'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;
36
48
'q': Quit server when all the clients will be disconnected;
37
49
"""
38
50
39
51
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
41
63
addr = writer .get_extra_info ('peername' )
42
64
logging .info (f"Serving { addr } " )
65
+ CLIENTS .append (addr )
43
66
while True :
44
67
writer .write (">> " .encode ())
45
- await writer .drain ()
68
+ await writer .drain () # Flushing data
46
69
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 )
49
73
logging .error (f"Closed connection - Reset Connection Error: { addr } " )
50
74
break
51
75
cmd = data .decode ().strip ()
52
76
53
77
logging .info (f"Received { cmd } from { addr } " )
54
78
55
79
if not cmd :
56
- writer . close ( )
80
+ await close_connection ( writer )
57
81
break
58
82
elif cmd == 'q' :
59
- logging .warning (f"Closed connection: { addr } " )
60
- writer .close ()
83
+ await close_connection (writer )
61
84
break
62
85
else :
63
- writer .write (handle_command (cmd , writer ).encode ()) # TODO: writeline
86
+ writer .write (handle_command (cmd , writer ).encode ())
64
87
await writer .drain ()
65
88
66
89
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 )
77
93
78
- logging .info (f"Server running on { HOST } :{ PORT } ..." )
79
94
95
+ async def main (host , port ):
80
96
server = await asyncio .start_server (server_handler , HOST , PORT )
81
97
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
+
82
103
async with server :
83
104
await server .serve_forever ()
84
105
85
106
86
107
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
+
87
118
try :
88
- asyncio .run (main ())
119
+ asyncio .run (main (HOST , PORT ))
89
120
except KeyboardInterrupt :
90
121
logging .warning ("Server stopping..." )
0 commit comments