def _connect(self, seconds_till_next_try: float = 2, timeout: float = -1) -> bool: waited = 0 while not self._exit.is_set() and not self._is_connected: # TODO: Get timeout time from Connector.connect try: self._socket_connection = socket.create_connection(self._address) self._socket_connection.settimeout(self._recv_timeout) self._is_connected = True logger.info(f"Successfully connected to: {str(self._address)}") return True except ConnectionRefusedError: logger.warning("Could not connect to server with address: (%s)", str(self._address)) except OSError as e: logger.error("Is already connected to server") logger.debug(e) self._is_connected = True except socket.gaierror: raise ValueError( f"Address error. {self._address} is not a valid address. Address must be of type {SocketAddress}") self._exit.wait(seconds_till_next_try) waited += seconds_till_next_try if waited > timeout >= 0: logger.warning("Connection timeout") return False return False
def _is_data_packet(self) -> int: try: function_id = self._function_stack.pop() return function_id except IndexError: logger.error("Trying to pop a empty function stack") return -1
def run(self) -> None: try: self._socket_connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._socket_connection.bind(self._address) logger.info( f"Server is now listening on: {self._address[0]}:{self._address[1]}" ) self._socket_connection.listen(4) except OSError as e: # [WinError 10038] socket closed before logger.error(e) self._exit.set() except socket.gaierror: raise ValueError( f"Address error. {self._address} is not a valid address. Address must be of type {SocketAddress}" ) while not self._exit.is_set(): try: (connection, addr) = self._socket_connection.accept() logger.info("New client connected: (%s)", str(addr)) client_id = self._produce_next_client_id() client_communicator_id = to_server_id(client_id) client = self._client_communicator( client_communicator_id, self._address, connection, self._remove_disconnected_client) self._add_client(client_communicator_id, client) except OSError as e: if not isinstance(e, socket.timeout): if not self._exit.is_set(): logger.error("TCP connection closed while listening")
def get(self, client_id: Optional[int] = None) -> 'ClientCommunicator': """Returns the proper ClientCommunicator. The proper one is the one who called the server-side function. This function is thread dependent. So if you create a new thread inside the called function you have to store the :code:`id` of the current ClientCommunicator and then call this function with this id as optional parameter. """ if client_id is None: current_thread: Union[ ClientCommunicator, threading.Thread] = threading.current_thread() try: client_id = current_thread.id except AttributeError: """Function may only be called from same thread (thread that called the server function) or with a valid existing client_id!""" logger.error( "Captain we have a multithreading problem! Thread dependent function called from another thread" ) raise Exception( "Thread dependent function called from another thread") if client_id not in self.clients.keys(): raise Exception( "No client connected. The client_id doesn't match any connected clients." ) assert isinstance( self.clients[client_id], ClientCommunicator), "Previous checks didnt handle all cases" return self.clients[client_id]
def send_packet(self, packet: Packet) -> bool: """Set the proper ids and converts/packs the packet into bytes. Sends the bytes string.""" IDManager(self._id).set_ids_of_packet(packet) send_data = packet.pack() successfully_sent = self._send_bytes(send_data) if not successfully_sent: logger.error("Could not send packet: %s", str(packet)) return successfully_sent
def update_ids_by_packet(self, packet: Packet) -> None: """Called every time a new packet arrives.""" self._next_global_id = packet.header.id_container.global_id + 1 if isinstance(packet, FunctionPacket): self._is_function_packet() elif isinstance(packet, DataPacket) or isinstance(packet, FileMetaPacket): self._is_data_packet() else: logger.error("Unknown packet_class (%s)", type(packet).__name__)
def _remove_disconnected_client(self, communicator: Communicator) -> None: """Called when one side stops""" try: self.clients.pop(communicator.get_id()) except KeyError: import traceback traceback.print_stack() logger.error( f"Trying to remove a client that was already removed! {self.clients}: {communicator.get_id()}" )
def set_ids_of_packet(self, packet: Packet) -> Optional[Packet]: """Set ids of packet and adjust internal state""" global_id = self._next_global_id if isinstance(packet, FunctionPacket): func_id = self._is_function_packet() elif isinstance(packet, DataPacket) or isinstance(packet, FileMetaPacket): func_id = self._is_data_packet() else: logger.error("Unknown packet_class (%s)", type(packet).__name__) return None packet.set_ids(func_id, global_id) self._next_global_id += 1 return packet
def _send_bytes(self, byte_string: bytes) -> bool: if not self._is_connected: self._connect(timeout=2) try: encrypted_message = self.cryptographer.encrypt(byte_string) if self.cryptographer.is_encrypted_communication: send_message = encrypted_message + b"%%" else: send_message = encrypted_message sent = self._socket_connection.sendall(send_message) # returns None on success return sent is None except OSError: logger.error(f"Could not send bytes {byte_string}") return False
def _recv_file(self, file_meta_packet: FileMetaPacket, plain_byte_stream: ByteStream, encrypted_byte_stream: ByteStream,) -> None: """Receives bytes, till the file is fully received. The file is saved at the destination, given in the file_meta_packet.""" file_path = file_meta_packet.dst_path existing_bytes = plain_byte_stream.next_all_bytes() with open(file_path, "wb+") as file: file.write(existing_bytes) remaining_bytes = file_meta_packet.file_size - len(existing_bytes) appended_data = b"" with open(file_path, "ab") as file: while remaining_bytes > 0: data = self._recv_data(plain_byte_stream, encrypted_byte_stream) if data is None: logger.error("Connection aborted, while receiving file!") return write_data = data[:remaining_bytes] appended_data = data[remaining_bytes:] file.write(write_data) remaining_bytes -= len(write_data) plain_byte_stream += appended_data
def wait_for_response(self): """Waits till a data-packet is received and returns it. If a function packet is received instead it is executed first.""" waited = 0. while not self._exit.is_set(): next_global_id = IDManager(self._id).get_next_outer_id() try: next_packet = self._packets.pop(0) actual_outer_id = next_packet.header.id_container.global_id if actual_outer_id > next_global_id: logger.error(f"Packet lost! Expected outer_id: {next_global_id}. Got instead: {actual_outer_id}") # TODO: handle elif actual_outer_id < next_global_id: logger.error(f"Unhandled Packet! Expected outer_id: {next_global_id}. " f"actual: {actual_outer_id}, Communicator id: {self._id}") # TODO: handle (if possible) else: if isinstance(next_packet, Packet): self._handle_packet(next_packet) if isinstance(next_packet, FunctionPacket): """execute and keep waiting for data""" elif isinstance(next_packet, DataPacket): return next_packet elif isinstance(next_packet, FileMetaPacket): """File is already transmitted.""" return DataPacket(**{"return": File.from_meta_packet(next_packet)}) else: logger.error(f"Received not implemented Packet class: {type(next_packet)}") except IndexError: pass # List is empty -> wait self._exit.wait(self._time_till_next_check) waited += self._time_till_next_check if waited > self.wait_for_response_timeout >= 0: logger.warning("wait_for_response waited too long") raise TimeoutError("wait_for_response waited too long")