From 6912cc36b82d704992e94efabae849ffb97b1aa8 Mon Sep 17 00:00:00 2001 From: Michael Zanger <michaelzanger@Michaels-MacBook-Air.fritz.box> Date: Fri, 12 Jan 2024 19:47:53 +0100 Subject: [PATCH] MacOS und Heartbeats --- chat_server.py | 33 ++++++++--- main_server.py | 14 ++++- server.py | 145 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 161 insertions(+), 31 deletions(-) diff --git a/chat_server.py b/chat_server.py index 00b9172..8ad5315 100644 --- a/chat_server.py +++ b/chat_server.py @@ -4,7 +4,19 @@ import socket import re if __name__ == '__main__': + # Create an instance of the Server class + server_instance = Server(client_address="", server_id="", server_port=0, server_cache=dict(), clients_cache=dict()) + + # Get the broadcast address from the existing server_instance + broadcast_address = server_instance.get_broadcast_address() + + if broadcast_address is None: + print("Failed to obtain broadcast address. Exiting.") + exit(1) + + print(broadcast_address) + os = Server.get_os_type() client_address = "" server_id = "" server_port = 0 @@ -13,21 +25,26 @@ if __name__ == '__main__': BROADCAST_PORT = 49154 - print("im alive") + print("I'm alive") MSG = bytes("HI MAIN SERVER", 'utf-8') broadcast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - broadcast_socket.sendto(MSG, ('<broadcast>', BROADCAST_PORT)) - print("successfully send message") - + broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if os == "macOS": + broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + broadcast_socket.sendto(MSG, (broadcast_address, BROADCAST_PORT)) + else: + broadcast_socket.sendto(MSG, ('<broadcast>', BROADCAST_PORT)) + print("Successfully sent message") + message, server = broadcast_socket.recvfrom(1024) match = re.search(r'\b([A-Za-z])\b$', message.decode('utf-8')) server_id = match.group(1) print('Received message from server: ', message.decode('utf-8')) - server = Server(client_address, server_id, server_port, server_cache, clients_cache) - time.sleep(20) - - + # You can use the existing server_instance here, no need to create another one + server_instance.start() # Start the server process + + time.sleep(20) \ No newline at end of file diff --git a/main_server.py b/main_server.py index 3d3a325..3a460b3 100644 --- a/main_server.py +++ b/main_server.py @@ -2,13 +2,23 @@ from server import Server import time if __name__ == '__main__': - client_address = "" server_id = "MAIN" server_port = 1000 server_cache = dict() clients_cache = dict() + + # Create the Server process server = Server(client_address, server_id, server_port, server_cache, clients_cache) - time.sleep(20) + # Start the Server process + server.start() + + # Optionally, if you want the main program to wait for a certain duration + # and then terminate the server, you can use: + # time.sleep(20) + # server.terminate() # This will terminate the server process after 20 seconds + # If you want the main program to wait indefinitely until the server process + # completes its execution, use: + server.join() diff --git a/server.py b/server.py index e7be751..2f0ae56 100644 --- a/server.py +++ b/server.py @@ -3,11 +3,14 @@ import socket import threading import json import time +import ipaddress +import netifaces as ni +import platform client_broadcast_listener_port = 49153 server_broadcast_listener_port = 49154 -server_heartbeat_tcp_listener_port = 49155 +server_heartbeat_tcp_listener_port = 49152 client_receive_chat_tcp_port = 50001 client_forward_message_multicast_port = 51000 @@ -16,21 +19,90 @@ multicast_group_ip = '224.0.1.1' class Server(multiprocessing.Process): - #server_address = '127.0.0.1' - host = socket.gethostname() - server_address = socket.gethostbyname(host) client_cache_key_offset = 0 local_servers_cache = dict() local_clients_cache = dict() def __init__(self, client_address, server_id, server_port, server_cache, clients_cache): super(Server, self).__init__() + self.os = self.get_os_type() + print(self.os) + self.active_interface = self.get_active_interface() + self.server_address = self.get_local_ip_address() + self.subnet_mask = self.get_subnet_mask(self.active_interface) + print(self.active_interface) self.client_address = client_address self.server_id = server_id self.server_port = server_port self.local_servers_cache = server_cache self.local_clients_cache = clients_cache - self.run( ) + + @staticmethod + def get_local_ip_address(): + """ Get the local IP address of the machine. """ + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + print("Attempting to connect to an external host for IP resolution...") + s.connect(("8.8.8.8", 80)) # Google's public DNS server + local_ip = s.getsockname()[0] + print(f"Local IP obtained: {local_ip}") + return local_ip + except Exception as e: + print(f"Error obtaining local IP address: {e}") + return '127.0.0.1' # Fallback to localhost + + @staticmethod + def get_active_interface(): + interfaces = ni.interfaces() + for interface in interfaces: + if interface != 'lo0': + addr = ni.ifaddresses(interface) + try: + # Check if the interface has an IPv4 configuration + if ni.AF_INET in addr: + ipv4_info = addr[ni.AF_INET][0] + ip = ipv4_info['addr'] + netmask = ipv4_info['netmask'] + + # Optionally check for an active internet connection + # This attempts to create a socket using the interface's IP + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.bind((ip, 0)) + s.connect(("8.8.8.8", 80)) # Google's DNS server + return interface + except socket.error: + pass # Interface is not suitable + + except KeyError: + # Interface does not have IPv4 configuration + pass + + return None + + @staticmethod + def get_subnet_mask(interface): + try: + addr = ni.ifaddresses(interface) + if ni.AF_INET in addr: + ipv4_info = addr[ni.AF_INET][0] + subnet_mask = ipv4_info['netmask'] + return subnet_mask + else: + return None # Interface does not have IPv4 configuration + except KeyError: + return None # Interface does not have IPv4 configuration + + @staticmethod + def get_os_type(): + system = platform.system() + if system == "Windows": + return "Windows" + elif system == "Darwin": + return "macOS" + else: + return "Unknown" + def run(self): print(self.server_id+": "+"Up and running") @@ -52,11 +124,22 @@ class Server(multiprocessing.Process): cache_update_listener_thread.start() client_message_listener_thread.start() heartbeat_receive_thread.start() + + def get_broadcast_address(self): + IP = self.server_address + MASK = self.subnet_mask + host = ipaddress.IPv4Address(IP) + net = ipaddress.IPv4Network(IP + '/' + MASK, False) + + print('Host:', ipaddress.IPv4Address(int(host) & int(net.hostmask))) + print('Broadcast:', net.broadcast_address) + broadcast_address = str(net.broadcast_address) + return broadcast_address def send_heartbeat(self): while True: time.sleep(10) - for server_id, server_port in self.local_servers_cache.items(): + for server_id in self.local_servers_cache.items(): if server_id != self.server_id: acknowledgment_received = self.send_heartbeat_to_server(self.server_address, server_heartbeat_tcp_listener_port) #acknowledgment_received = "YES" @@ -69,6 +152,7 @@ class Server(multiprocessing.Process): acknowledgment_received = False try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) s.settimeout(2) # Timeout for the connection # Combine server address and port into a tuple server_address_with_port = (server_address, server_port) @@ -87,13 +171,13 @@ class Server(multiprocessing.Process): while True: try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) s.bind((self.server_address, server_heartbeat_tcp_listener_port)) # Bind to any available port actual_port = s.getsockname()[1] print(f"Heartbeat Listener Started on port {actual_port}") s.listen() conn, addr = s.accept() with conn: - print("Hallo") data = conn.recv(1024) if data == b'HEARTBEAT': # Hier Code zum Verarbeiten des Heartbeats @@ -116,18 +200,18 @@ class Server(multiprocessing.Process): def listen_for_servers(self): BROADCAST_PORT = 49154 - - # Local host information - MY_HOST = socket.gethostname() - MY_IP = socket.gethostbyname(MY_HOST) + BROADCAST_ADDRESS = self.get_broadcast_address() # Create a UDP socket listen_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Set the socket to broadcast and enable reusing addresses listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - # Bind socket to address and port - listen_socket.bind((self.server_address, BROADCAST_PORT)) + if self.os == "macOS": + listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + listen_socket.bind((BROADCAST_ADDRESS, BROADCAST_PORT)) + else: + listen_socket.bind(('', BROADCAST_PORT)) print(self.server_id+": "+"Listening to server register broadcast messages") @@ -150,9 +234,14 @@ class Server(multiprocessing.Process): update_cache_thread.start() def register_server(self, addr, server_id): + BROADCAST_ADDRESS = self.get_broadcast_address() message = 'Hi ' + addr[0] + ' this is your chat-group ID: ' + str(server_id) server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - server_socket.connect((self.server_address, self.server_port)) + if self.os == "macOS": + server_socket.connect((BROADCAST_ADDRESS, self.server_port)) + else: + server_socket.connect((self.server_address, self.server_port)) + server_socket.sendto(str.encode(message), addr) server_socket.close() @@ -160,18 +249,22 @@ class Server(multiprocessing.Process): def listen_for_clients(self): BROADCAST_PORT = 49153 + BROADCAST_ADDRESS = self.get_broadcast_address() # Local host information - MY_HOST = socket.gethostname() - MY_IP = socket.gethostbyname(MY_HOST) + #MY_HOST = socket.gethostname() + #MY_IP = socket.gethostbyname(MY_HOST) # Create a UDP socket listen_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Set the socket to broadcast and enable reusing addresses listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - # Bind socket to address and port - listen_socket.bind((self.server_address, BROADCAST_PORT)) + if self.os == "macOS": + listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + listen_socket.bind((BROADCAST_ADDRESS, BROADCAST_PORT)) + else: + listen_socket.bind((self.server_address, BROADCAST_PORT)) print(self.server_id+": "+"Listening to client register broadcast messages") @@ -261,6 +354,7 @@ class Server(multiprocessing.Process): def updateCacheList(self): PORT = 5980 + BROADCAST_ADDRESS = self.get_broadcast_address() servers_cache_as_string = json.dumps(self.local_servers_cache, indent=2).encode('utf-8') clients_cache_as_string = json.dumps(self.local_clients_cache, indent=2).encode('utf-8') separator = "_" @@ -270,10 +364,15 @@ class Server(multiprocessing.Process): broadcast_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - broadcast_socket.sendto(MSG, ('<broadcast>', PORT)) + if self.os == "macOS": + broadcast_socket.sendto(MSG, (BROADCAST_ADDRESS, PORT)) + broadcast_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + else: + broadcast_socket.sendto(MSG, ('<broadcast>', PORT)) broadcast_socket.close() def listen_for_cache_update(self): + BROADCAST_ADDRESS = self.get_broadcast_address() BROADCAST_PORT = 5980 # Local host information @@ -285,8 +384,12 @@ class Server(multiprocessing.Process): # Set the socket to broadcast and enable reusing addresses listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - # Bind socket to address and port - listen_socket.bind((MY_IP, BROADCAST_PORT)) + + if self.os == "macOS": + listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + listen_socket.bind((BROADCAST_ADDRESS, BROADCAST_PORT)) + else: + listen_socket.bind((MY_IP, BROADCAST_PORT)) print(self.server_id+": "+"Listening to cache update broadcast messages") -- GitLab