1
+ import socket
2
+ import select
3
+
4
+ HEADER_LENGTH = 10
5
+
6
+ IP = "192.168.204.1"
7
+ PORT = 5052
8
+
9
+ # Create a socket
10
+ # socket.AF_INET - address family, IPv4, some otehr possible are AF_INET6, AF_BLUETOOTH, AF_UNIX
11
+ # socket.SOCK_STREAM - TCP, conection-based, socket.SOCK_DGRAM - UDP, connectionless, datagrams, socket.SOCK_RAW - raw IP packets
12
+ server_socket = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
13
+
14
+ # SO_ - socket option
15
+ # SOL_ - socket option level
16
+ # Sets REUSEADDR (as a socket option) to 1 on socket
17
+ server_socket .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
18
+
19
+ # Bind, so server informs operating system that it's going to use given IP and port
20
+ # For a server using 0.0.0.0 means to listen on all available interfaces, useful to connect locally to 127.0.0.1 and remotely to LAN interface IP
21
+ server_socket .bind ((IP , PORT ))
22
+
23
+ # This makes server listen to new connections
24
+ server_socket .listen ()
25
+
26
+ # List of sockets for select.select()
27
+ sockets_list = [server_socket ]
28
+
29
+ # List of connected clients - socket as a key, user header and name as data
30
+ clients = {}
31
+
32
+ print (f'Listening for connections on { IP } :{ PORT } ...' )
33
+
34
+ # Handles message receiving
35
+ def receive_message (client_socket ):
36
+
37
+ try :
38
+
39
+ # Receive our "header" containing message length, it's size is defined and constant
40
+ message_header = client_socket .recv (HEADER_LENGTH )
41
+
42
+ # If we received no data, client gracefully closed a connection, for example using socket.close() or socket.shutdown(socket.SHUT_RDWR)
43
+ if not len (message_header ):
44
+ return False
45
+
46
+ # Convert header to int value
47
+ message_length = int (message_header .decode ('utf-8' ).strip ())
48
+
49
+ # Return an object of message header and message data
50
+ return {'header' : message_header , 'data' : client_socket .recv (message_length )}
51
+
52
+ except :
53
+
54
+ # If we are here, client closed connection violently, for example by pressing ctrl+c on his script
55
+ # or just lost his connection
56
+ # socket.close() also invokes socket.shutdown(socket.SHUT_RDWR) what sends information about closing the socket (shutdown read/write)
57
+ # and that's also a cause when we receive an empty message
58
+ return False
59
+
60
+ while True :
61
+
62
+ # Calls Unix select() system call or Windows select() WinSock call with three parameters:
63
+ # - rlist - sockets to be monitored for incoming data
64
+ # - wlist - sockets for data to be send to (checks if for example buffers are not full and socket is ready to send some data)
65
+ # - xlist - sockets to be monitored for exceptions (we want to monitor all sockets for errors, so we can use rlist)
66
+ # Returns lists:
67
+ # - reading - sockets we received some data on (that way we don't have to check sockets manually)
68
+ # - writing - sockets ready for data to be send thru them
69
+ # - errors - sockets with some exceptions
70
+ # This is a blocking call, code execution will "wait" here and "get" notified in case any action should be taken
71
+ read_sockets , _ , exception_sockets = select .select (sockets_list , [], sockets_list )
72
+
73
+
74
+ # Iterate over notified sockets
75
+ for notified_socket in read_sockets :
76
+
77
+ # If notified socket is a server socket - new connection, accept it
78
+ if notified_socket == server_socket :
79
+
80
+ # Accept new connection
81
+ # That gives us new socket - client socket, connected to this given client only, it's unique for that client
82
+ # The other returned object is ip/port set
83
+ client_socket , client_address = server_socket .accept ()
84
+
85
+ # Client should send his name right away, receive it
86
+ user = receive_message (client_socket )
87
+
88
+ # If False - client disconnected before he sent his name
89
+ if user is False :
90
+ continue
91
+
92
+ # Add accepted socket to select.select() list
93
+ sockets_list .append (client_socket )
94
+
95
+ # Also save username and username header
96
+ clients [client_socket ] = user
97
+
98
+ print ('Accepted new connection from {}:{}, username: {}' .format (* client_address , user ['data' ].decode ('utf-8' )))
99
+
100
+ # Else existing socket is sending a message
101
+ else :
102
+
103
+ # Receive message
104
+ message = receive_message (notified_socket )
105
+
106
+ # If False, client disconnected, cleanup
107
+ if message is False :
108
+ print ('Closed connection from: {}' .format (clients [notified_socket ]['data' ].decode ('utf-8' )))
109
+
110
+ # Remove from list for socket.socket()
111
+ sockets_list .remove (notified_socket )
112
+
113
+ # Remove from our list of users
114
+ del clients [notified_socket ]
115
+
116
+ continue
117
+
118
+ # Get user by notified socket, so we will know who sent the message
119
+ user = clients [notified_socket ]
120
+
121
+ print (f'Received message from { user ["data" ].decode ("utf-8" )} : { message ["data" ].decode ("utf-8" )} ' )
122
+
123
+ # Iterate over connected clients and broadcast message
124
+ for client_socket in clients :
125
+
126
+ # But don't sent it to sender
127
+ if client_socket != notified_socket :
128
+
129
+ # Send user and message (both with their headers)
130
+ # We are reusing here message header sent by sender, and saved username header send by user when he connected
131
+ client_socket .send (user ['header' ] + user ['data' ] + message ['header' ] + message ['data' ])
132
+
133
+ # It's not really necessary to have this, but will handle some socket exceptions just in case
134
+ for notified_socket in exception_sockets :
135
+
136
+ # Remove from list for socket.socket()
137
+ sockets_list .remove (notified_socket )
138
+
139
+ # Remove from our list of users
140
+ del clients [notified_socket ]
0 commit comments