-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.py
202 lines (168 loc) · 7.09 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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
'''
project: slimmer maken multiconnectivity modem
author: Frank Montenij
date: 29-10-2021
description: This code accept connections after which it receives confirmations of DAB message.
The code will store these messages and send back an acknowledgment to the client to inform the client that the confirmation has been received.
The code will also be used when testing the whole confirmation using wifi.
'''
import socket
import threading
import json
import time
from dataclasses import dataclass, field
# General informtion also necessary when importing server
max_msg_length = 10
close_message = "DISCONNECT"
class ClientClosedConnectionError(Exception):
"""This error is raised when the client closes the connection without the disconnect message."""
@dataclass(order=True)
class DAB_confirmation:
"""A dataclass to store the information of a DAB_confirmation."""
sort_index: int = field(init=False, repr=False)
dab_id: int
message_type: int
dab_msg_arrived_at: float
technology: str
sender: int
valid: bool = True
# Enables to sort the object on the dab_id
def __post_init__(self):
self.sort_index = self.dab_id
# A method to represent the data in the object in a readable way
def __str__(self):
return f"DAB_ID: {self.dab_id}, Message_type: {self.message_type}, Time_DAB_message_arrived: {time.ctime(self.dab_msg_arrived_at)}, Sender: {self.sender}, Valid: {self.valid}"
# Used to retrieve valuable information of this object for the reply process
def get_reply_info(self):
return (self.dab_id, self.valid)
"""
This function is responsible for accepting connections from clients.
It also starts a thread to handle the accepted connection.
"""
def run():
server.listen()
print(f"Server is listening on {ip_address}")
while True:
conn, _ = server.accept()
thread = threading.Thread(target=client_thread, args=[conn])
thread.start()
print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")
"""
This function handles receiving messages from the client.
And sending back a reply message to the sender.
"""
def client_thread(conn):
while True:
try:
confirmation = receive_confirmation(conn)
except ClientClosedConnectionError:
break
# If the data contains the disconnect message close the connection
if close_message in confirmation:
print(f"[CLIENT] {close_message}!")
break
else: # Otherwise store the confirmation
store_confirmation(confirmation)
# Show the DAB messages that are confirmed
DAB_confirmations.sort()
show_confirmations()
# find dab_confirmation by dab_id
confirmation_by_dab = find_dab_confirmation_by_sender(confirmation.get("dab_id"))
send_reply(conn, confirmation.get("dab_id"), confirmation_by_dab.sender)
# Close the connection
conn.close()
"""
Receives the confirmation message.
"""
def receive_confirmation(conn):
# Receive the length of the confirmation message
confirmation_length = conn.recv(max_msg_length).decode()
# If the client closes the connection stop the thread
if len(confirmation_length) == 0:
raise(ClientClosedConnectionError)
# Receive the confirmation itself
confirmation_length = int(confirmation_length)
confirmation = conn.recv(confirmation_length).decode()
# Return the extracted data from the message
return json.loads(confirmation)
"""
Send the reply with the reply dict info
"""
def send_reply(conn, dab_id, sender):
reply = json.dumps(build_reply_dict(dab_id, sender)).encode()
reply_length = pad_msg_length(max_msg_length, len(reply))
conn.send(reply_length)
conn.send(reply)
"""
Stores the ack and mstype value in DAB_confirmations when ack is not already in the DAB_confirmations
"""
def store_confirmation(confirmation):
new_dab_confirmation = DAB_confirmation(
confirmation.get("dab_id"),
confirmation.get("message_type"),
confirmation.get("dab_msg_arrived_at"),
confirmation.get("technology"),
confirmation.get("sender")
)
if not check_if_in_DAB_confirmations(new_dab_confirmation.dab_id):
DAB_confirmations.append(new_dab_confirmation)
else:
print("[SERVER] DAB_confirmation already in list")
"""
Checks if the dab_id is already in the DAB_confirmations
"""
def check_if_in_DAB_confirmations(dab_id):
dab_ids = [confirmation_by_dab.dab_id for confirmation_by_dab in DAB_confirmations]
if dab_id in dab_ids:
return True
else:
return False
"""
This function shows all the DAB confirmations after it sorted the list on dab_id
"""
def show_confirmations():
for DAB_confirmation in DAB_confirmations:
print(DAB_confirmation)
"""
This function returns a dict containing the information necessary and useful for the raspberry pi
For example the DAB_ID of the message the system tries acknowledge, the messages this server received via AIS that have been send by the rpi that tries to acknowledge a DAB message
and the messages that have become invalid.
"""
def build_reply_dict(dab_id_to_confirm, sender):
reply = dict()
# Add DAB_confirmation to this list if the dab_id is the same as dab_id_to_confirm
confirmation_by_dab = find_dab_confirmation_by_sender(dab_id_to_confirm)
reply['ack_information'] = [confirmation_by_dab.dab_id, confirmation_by_dab.valid]
"""
Add DAB_confirmation to this list if the confirmation is received from sender and not the same as ack_information.
To update the folder which files have been received. Not just the Wifi messages but all the messages. This way the AIS, LoRaWAN and LTE messages can be confirmed.
"""
reply['different_ack_information'] = [entry.get_reply_info() for entry in DAB_confirmations if entry.sender == sender and not confirmation_by_dab.dab_id == entry.dab_id]
return reply
"""
This function finds all the dab_confirmations with the dab_id dab_id.
"""
def find_dab_confirmation_by_sender(dab_id):
results = [confirmation_by_dab for confirmation_by_dab in DAB_confirmations if confirmation_by_dab.dab_id == dab_id]
return results[0]
"""
pad the var msg_length to the padding size.
So that the message containing the msg_length has a fixed size of padding size.
"""
def pad_msg_length(padding_size, msg_length):
msg_length = str(msg_length).encode()
msg_length += b' ' * (padding_size - len(msg_length))
return msg_length
# Start the server
if __name__ == "__main__":
# general information for the server and messages with the client
port = 9000
ip_address = "192.168.3.2"
address = (ip_address, port)
# DAB_confirmations is a List that holds all the DAB_confirmations that have been received by the server using this program
DAB_confirmations = []
# This creates the server
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(address)
print("[STARTING] server is starting...")
run()