Skip to content

Commit 3570e35

Browse files
committed
client and server commands
1 parent 1e0139a commit 3570e35

File tree

2 files changed

+108
-34
lines changed

2 files changed

+108
-34
lines changed

tmarchuk/client.py

+46-13
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
import signal
44
import socket
55
import sys
6+
import threading
67

78

89
def sig_handler(signum, frame):
910
global client
10-
if client and getattr(client, 'connected'):
11-
client.connected = False
11+
if client and getattr(client, 'stop'):
12+
client.stop.set()
1213

1314

1415
class Client:
15-
connected = False
16-
CHUNK_SIZE = 1024
16+
cmd_list = ['connected', 'pong', 'pongd', 'ackreply', 'ackfinish']
1717

1818
def __init__(self, host='localhost', port=39999):
1919
self.host = host
@@ -22,30 +22,63 @@ def __init__(self, host='localhost', port=39999):
2222
try:
2323
self.socket.connect((self.host, self.port))
2424
except socket.error as e:
25-
print("Can't connect to server: %s, %s" % (e.errno, e.strerror))
25+
print("Can't connect to server: {}, {}".format(e.errno, e.strerror))
2626
sys.exit(2)
27-
else:
28-
self.connected = True
27+
self.stop = threading.Event()
2928

3029
def start(self):
31-
while self.connected:
30+
self.rcv_thread = threading.Thread(target=self.rcv_handler)
31+
self.rcv_thread.start()
32+
while not self.stop.wait(0):
3233
input_data = input('=> ')
3334
if len(input_data.strip()):
34-
self.send(input_data)
35-
data = self.recv()
36-
print(data)
35+
data = '\n'.join(input_data.split(' ', 1))
36+
self.send(data)
37+
self.terminate()
38+
39+
def terminate(self):
3740
self.socket.close()
41+
self.rcv_thread.join()
42+
43+
def parse_data(self, data):
44+
data_list = data.split('\n', 1)
45+
return data_list[0], data_list[1:]
46+
47+
def rcv_handler(self):
48+
while not self.stop.wait(0):
49+
data = self.recv()
50+
if not data:
51+
print("Connection closed by server")
52+
self.stop.set()
53+
break
54+
cmd, cmd_args = self.parse_data(data)
55+
if cmd in self.cmd_list and callable(getattr(self, cmd, None)):
56+
getattr(self, cmd)(cmd_args)
57+
else:
58+
print(data)
59+
60+
def connected(self, data):
61+
print('New client connected from {}'.format(data))
62+
63+
def ackquit(self, data):
64+
print('Someone has gone: "{}"'.format(data))
65+
66+
def ackfinish(self, data):
67+
print("Server is going down!")
68+
self.terminate()
3869

3970
def send(self, data):
4071
snd_data = data.encode('utf-8')
72+
snd_length = int(len(snd_data)).to_bytes(4, 'big')
73+
self.socket.sendall(snd_length)
4174
self.socket.sendall(snd_data)
4275

4376
def recv(self):
44-
rcv_data = self.socket.recv(self.CHUNK_SIZE)
77+
rcv_length = int.from_bytes(self.socket.recv(4), 'big')
78+
rcv_data = self.socket.recv(rcv_length)
4579
data = rcv_data.decode('utf-8')
4680
return data
4781

48-
4982
if __name__ == '__main__':
5083
client = Client()
5184
signal.signal(signal.SIGINT, sig_handler)

tmarchuk/server.py

+62-21
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,24 @@ def sig_handler(signum, frame):
1717
server.stop()
1818

1919

20-
class Connection(threading.Thread):
21-
cmd_list = []
22-
CHUNK_SIZE = 1024
20+
class Connection:
21+
cmd_list = ['connect', 'ping', 'pingd', 'quit', 'finish']
2322

24-
def __init__(self, conn, addr):
23+
def __init__(self, conn, addr, srv):
2524
self.conn = conn
2625
self.addr = addr
26+
self.srv = srv
2727
self.stop = threading.Event()
28-
threading.Thread.__init__(self)
29-
30-
def terminate(self):
31-
self.stop.set()
28+
self.thread = threading.Thread(target=self.run)
29+
self.thread.start()
3230

3331
def run(self):
3432
while not self.stop.wait(0):
3533
try:
3634
data = self.recv()
3735
if data and len(data.strip()):
3836
cmd, cmd_args = self.parse_data(data)
39-
if cmd in self.cmd_list and callable(getattr(self, cmd)):
37+
if cmd in self.cmd_list and callable(getattr(self, cmd, None)):
4038
getattr(self, cmd)(cmd_args)
4139
else:
4240
self.send('No such command')
@@ -49,31 +47,59 @@ def run(self):
4947
else:
5048
logger.info("IO error: {}, {}".format(e.errno, e.strerror))
5149
break
50+
self.stop.set()
5251
self.conn.close()
5352

53+
def terminate(self):
54+
self.stop.set()
55+
5456
def send(self, data):
5557
logger.debug("sending: {}".format(repr(data)))
5658
snd_data = data.encode('utf-8')
59+
snd_length = int(len(snd_data)).to_bytes(4, 'big')
60+
self.conn.sendall(snd_length)
5761
self.conn.sendall(snd_data)
5862

5963
def recv(self):
60-
rcv_data = self.conn.recv(self.CHUNK_SIZE)
64+
rcv_length = int.from_bytes(self.conn.recv(4), 'big')
65+
rcv_data = self.conn.recv(rcv_length)
6166
if not rcv_data:
6267
logger.info("Connection closed by foreign host.")
63-
self.terminate()
68+
self.stop.set()
6469
return False
6570
data = rcv_data.decode('utf-8')
66-
logger.debug("received: %s".format(repr(data)))
71+
logger.debug("received: {}".format(repr(data)))
6772
return data
6873

6974
def parse_data(self, data):
70-
data_list = data.split()
75+
data_list = data.split('\n', 1)
7176
return data_list[0], data_list[1:]
7277

78+
def connect(self, data):
79+
self.srv.multicast_message('connected\n{}'.format(
80+
':'.join(map(str, self.addr))
81+
))
82+
83+
def ping(self, data):
84+
self.send('pong')
85+
86+
def pingd(self, data):
87+
self.send('pongd\n{}'.format(' '.join(data)))
88+
89+
def quit(self, data):
90+
if data:
91+
self.srv.multicast_message('ackquit\n{}'.format(data))
92+
self.stop.set()
93+
94+
def finish(self, data):
95+
self.stop.set()
96+
self.srv.terminate()
97+
7398

7499
class Server:
75100
running = False
76101
connections = []
102+
connections_lock = threading.RLock()
77103

78104
def __init__(self, host='', port=39999):
79105
logger.info("Init server at {}:{}".format(host, port))
@@ -96,20 +122,35 @@ def run(self):
96122
while self.running:
97123
try:
98124
conn, addr = self.socket.accept()
99-
except (socket.timeout, InterruptedError):
125+
except socket.timeout:
100126
pass
127+
except (KeyboardInterrupt, InterruptedError):
128+
self.stop()
101129
else:
102-
th_connection = Connection(conn, addr)
103-
th_connection.start()
104-
self.connections.append(th_connection)
130+
client_connection = Connection(conn, addr, self)
131+
with self.connections_lock:
132+
self.connections.append(client_connection)
133+
self.stop()
134+
135+
def terminate(self):
136+
self.running = False
105137

106138
def stop(self):
107139
self.running = False
140+
self.multicast_message('ackfinish')
141+
# self.ward_thread.join()
142+
with self.connections_lock:
143+
for conn in self.connections:
144+
conn.terminate()
145+
conn.thread.join()
146+
del conn
108147
self.socket.close()
109-
for conn in self.connections:
110-
conn.terminate()
111-
conn.join()
112-
del conn
148+
149+
def multicast_message(self, msg):
150+
with self.connections_lock:
151+
for conn in self.connections:
152+
if not conn.stop.wait(0):
153+
conn.send(msg)
113154

114155

115156
if __name__ == '__main__':

0 commit comments

Comments
 (0)