-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.py
149 lines (121 loc) · 4.91 KB
/
server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python3
import threading
import socket
import argparse
import os
class Server(threading.Thread):
"""
Supports management of server connections.
Attributes:
connections (list): A list of ServerSocket objects representing the active connections.
host (str): The IP address of the listening socket.
port (int): The port number of the listening socket.
"""
def __init__(self, host, port):
super().__init__()
self.connections = []
self.host = host
self.port = port
def run(self):
"""
Creates the listening socket. The listening socket will use the SO_REUSEADDR option to
allow binding to a previously-used socket address. This is a small-scale application which
only supports one waiting connection at a time.
For each new connection, a ServerSocket thread is started to facilitate communications with
that particular client. All ServerSocket objects are stored in the connections attribute.
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((self.host, self.port))
sock.listen(1)
print('Listening at', sock.getsockname())
while True:
# Accept new connection
sc, sockname = sock.accept()
print('Accepted a new connection from {} to {}'.format(sc.getpeername(), sc.getsockname()))
# Create new thread
server_socket = ServerSocket(sc, sockname, self)
# Start new thread
server_socket.start()
# Add thread to active connections
self.connections.append(server_socket)
print('Ready to receive messages from', sc.getpeername())
def broadcast(self, message, source):
"""
Sends a message to all connected clients, except the source of the message.
Args:
message (str): The message to broadcast.
source (tuple): The socket address of the source client.
"""
for connection in self.connections:
# Send to all connected clients except the source client
if connection.sockname != source:
connection.send(message)
def remove_connection(self, connection):
"""
Removes a ServerSocket thread from the connections attribute.
Args:
connection (ServerSocket): The ServerSocket thread to remove.
"""
self.connections.remove(connection)
class ServerSocket(threading.Thread):
"""
Supports communications with a connected client.
Attributes:
sc (socket.socket): The connected socket.
sockname (tuple): The client socket address.
server (Server): The parent thread.
"""
def __init__(self, sc, sockname, server):
super().__init__()
self.sc = sc
self.sockname = sockname
self.server = server
def run(self):
"""
Receives data from the connected client and broadcasts the message to all other clients.
If the client has left the connection, closes the connected socket and removes itself
from the list of ServerSocket threads in the parent Server thread.
"""
while True:
message = self.sc.recv(1024).decode('ascii')
if message:
print('{} says {!r}'.format(self.sockname, message))
self.server.broadcast(message, self.sockname)
else:
# Client has closed the socket, exit the thread
print('{} has closed the connection'.format(self.sockname))
self.sc.close()
server.remove_connection(self)
return
def send(self, message):
"""
Sends a message to the connected server.
Args:
message (str): The message to be sent.
"""
self.sc.sendall(message.encode('ascii'))
def exit(server):
"""
Allows the server administrator to shut down the server.
Typing 'q' in the command line will close all active connections and exit the application.
"""
while True:
ipt = input('')
if ipt == 'q':
print('Closing all connections...')
for connection in server.connections:
connection.sc.close()
print('Shutting down the server...')
os._exit(0)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Chatroom Server')
parser.add_argument('host', help='Interface the server listens at')
parser.add_argument('-p', metavar='PORT', type=int, default=1060,
help='TCP port (default 1060)')
args = parser.parse_args()
# Create and start server thread
server = Server(args.host, args.p)
server.start()
exit = threading.Thread(target = exit, args = (server,))
exit.start()