diff --git a/network/README.md b/network/README.md index c5cb7b5..4b8ca37 100644 --- a/network/README.md +++ b/network/README.md @@ -1,5 +1,4 @@ # Network -<<<<<<< HEAD This module facilitates communication over TCP and UDP. ## Testing @@ -30,16 +29,3 @@ This process repeats until all test messages are sent. *Note: UDP does not guarantee that data is sent or is not corrupted. It is a connectionless protocol and thus the server cannot send any messages. -======= -To test `client_socket` and `server_socket`, we will set up a socket server that listens for image byte data from a sender. - -Run the test scripts in the following order: - -1. `test_receiver.py` -2. `test_sender.py` - -`test_receiver.py` will start a socket server listening on `127.0.0.1:8080`. `test_sender.py` will first encode each image returned from `get_images`, then send the raw byte data to the server. - -The main loop expects a response from the server, consisting of the original image, data before continuing to the next image. The asserted condition in the main loop tests for equality between the image byte data that was sent to the server, and the byte data that was received, ensuring data integrity. - ->>>>>>> 8c58ed6 (Refactor pytest into sender and receiver scripts) diff --git a/network/modules/TCP/server_socket.py b/network/modules/TCP/server_socket.py index a14936e..11adffa 100644 --- a/network/modules/TCP/server_socket.py +++ b/network/modules/TCP/server_socket.py @@ -18,7 +18,6 @@ def __init__(self, class_private_create_key: object, socket_instance: socket.soc """ Private constructor, use create() method. """ - assert class_private_create_key is TcpServerSocket.__create_key, "Use create() method" super().__init__(socket_instance=socket_instance) diff --git a/network/modules/TCP/socket_wrapper.py b/network/modules/TCP/socket_wrapper.py index 222dccc..87c42de 100644 --- a/network/modules/TCP/socket_wrapper.py +++ b/network/modules/TCP/socket_wrapper.py @@ -17,7 +17,6 @@ def __init__(self, socket_instance: socket.socket) -> None: instance: socket.socket For initializing Socket with an existing socket object. """ - self.__socket = socket_instance def send(self, data: bytes) -> bool: @@ -32,7 +31,6 @@ def send(self, data: bytes) -> bool: ------- bool: If the data was sent successfully. """ - try: self.__socket.sendall(data) except socket.error as e: @@ -82,7 +80,6 @@ def close(self) -> bool: ------- bool: If the socket was closed successfully. """ - try: self.__socket.close() except socket.error as e: @@ -100,12 +97,10 @@ def address(self) -> "tuple[str, int]": tuple[str, int] The address in the format (ip address, port). """ - return self.__socket.getsockname() def get_socket(self) -> socket.socket: """ Getter for the underlying socket objet. """ - return self.__socket diff --git a/network/modules/client_socket.py b/network/modules/client_socket.py deleted file mode 100644 index c98834a..0000000 --- a/network/modules/client_socket.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Wrapper for client socket operations. -""" - -import socket - -from network.modules.socket_wrapper import NetworkProtocol, Socket - - -class ClientSocket(Socket): - """ - Wrapper for client socket operations. - """ - - __create_key = object() - - def __init__(self, class_private_create_key: object, socket_instance: socket.socket) -> None: - """ - Private constructor, use create() method. - """ - assert class_private_create_key is ClientSocket.__create_key, "Use create() method" - - super().__init__(socket_instance=socket_instance) - - @classmethod - def create( - cls, - instance: socket.socket = None, - host: str = "127.0.0.1", - port: int = 5000, - protocol: NetworkProtocol = NetworkProtocol.TCP, - create_max_attempts: int = 10, - connect_max_attempts: int = 10, - ) -> "tuple[bool, ClientSocket | None]": - """ - Establishes socket connection through provided host and port. - - Parameters - ---------- - instance: socket.socket (default None) - For initializing Socket with an existing socket object. - - host: str (default "127.0.0.1") - port: int (default 5000) - The host combined with the port will form an address (e.g. localhost:5000) - - protocol: Protocol (default Protocol.TCP) - Enum representing which protocol to use for the socket - - create_max_attempts: int (default 10) - connect_max_attempts: int (default 10) - - Returns - ------- - tuple[bool, ClientSocket | None] - The first parameter represents if the socket creation is successful. - - If it is not successful, the second parameter will be None. - - If it is successful, the second parameter will be the created - ClientSocket object. - """ - # Reassign instance before check or Pylance will complain - socket_instance = instance - if socket_instance is not None: - return True, ClientSocket(cls.__create_key, socket_instance) - - for _ in range(create_max_attempts): - try: - socket_instance = socket.socket(socket.AF_INET, protocol.value) - break - except socket.error as e: - print(f"Could not create socket: {e}.") - - if socket_instance is None: - return False, None - - connected = False - for _ in range(connect_max_attempts): - try: - socket_instance.connect((host, port)) - connected = True - break - except socket.gaierror as e: - print( - f"Could not connect to socket, address related error: {e}. " - "Make sure the host and port are correct." - ) - except socket.error as e: - print(f"Could not connect to socket, connection error: {e}.") - - if not connected: - return False, None - - return True, ClientSocket(cls.__create_key, socket_instance) diff --git a/network/modules/server_socket.py b/network/modules/server_socket.py deleted file mode 100644 index 0b369ea..0000000 --- a/network/modules/server_socket.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Wrapper for server socket operations. -""" - -import socket - -from network.modules.client_socket import ClientSocket -from network.modules.socket_wrapper import NetworkProtocol, Socket - - -class ServerSocket(Socket): - """ - Wrapper for server socket operations. - """ - - __create_key = object() - - def __init__(self, class_private_create_key: object, socket_instance: socket.socket) -> None: - """ - Private constructor, use create() method. - """ - assert class_private_create_key is ServerSocket.__create_key, "Use create() method" - - super().__init__(socket_instance=socket_instance) - - @classmethod - def create( - cls, - instance: socket.socket = None, - host: str = "127.0.0.1", - port: int = 5000, - protocol: NetworkProtocol = NetworkProtocol.TCP, - create_max_attempts: int = 10, - connect_max_attempts: int = 10, - ) -> "tuple[bool, ServerSocket | None]": - """ - Establishes socket connection through provided host and port. - - Parameters - ---------- - instance: socket.socket (default None) - For initializing Socket with an existing socket object. - - host: str (default "127.0.0.1") - port: int (default 5000) - The host combined with the port will form an address (e.g. localhost:5000) - - protocol: Protocol (default Protocol.TCP) - Enum representing which protocol to use for the socket - - create_max_attempts: int (default 10) - connect_max_attempts: int (default 10) - - Returns - ------- - tuple[bool, ServerSocket | None] - The first parameter represents if the socket creation is successful. - - If it is not successful, the second parameter will be None. - - If it is successful, the second parameter will be the created - ServerSocket object. - """ - # Reassign instance before check or Pylance will complain - socket_instance = instance - if socket_instance is not None: - return True, ServerSocket(cls.__create_key, socket_instance) - - for _ in range(create_max_attempts): - try: - socket_instance = socket.socket(socket.AF_INET, protocol.value) - break - except socket.error as e: - print(f"Could not create socket: {e}.") - - if socket_instance is None: - return False, None - - connected = False - for _ in range(connect_max_attempts): - try: - socket_instance.bind((host, port)) - socket_instance.listen() - connected = True - break - except socket.gaierror as e: - print( - f"Could not connect to socket, address related error: {e}. " - "Make sure the host and port are correct." - ) - except socket.error as e: - print(f"Could not connect to socket, connection error: {e}.") - - if not connected: - return False, None - - return True, ServerSocket(cls.__create_key, socket_instance) - - def accept(self) -> "tuple[bool, ClientSocket | None]": - """ - Waits (blocking) for an incoming connection. Then returns a ClientSocket instance - representing the connection. - - Returns - ------- - tuple[bool, ClientSocket | None] - The first parameter represents if the socket creation is successful. - - If it is not successful, the second parameter will be None. - - If it is successful, the second parameter will be the created - ClientSocket object. - """ - try: - client_socket, addr = self.get_socket().accept() - except socket.error as e: - print(f"Could not accept incoming connection: {e}.") - return False, None - - print(f"Connected by {addr}.") - return ClientSocket.create(instance=client_socket) diff --git a/network/modules/socket_wrapper.py b/network/modules/socket_wrapper.py deleted file mode 100644 index 1fb6d41..0000000 --- a/network/modules/socket_wrapper.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -Wrapper for socket operations. -""" - -import socket -from enum import Enum - - -class NetworkProtocol(Enum): - """ - Enum used to select protocol when instantiating Socket. - """ - - TCP = socket.SOCK_STREAM - UDP = socket.SOCK_DGRAM - - -class Socket: - """ - Wrapper for Python's socket module. - """ - - def __init__(self, socket_instance: socket.socket) -> None: - """ - Parameters - ---------- - instance: socket.socket - For initializing Socket with an existing socket object. - """ - self.__socket = socket_instance - - def send(self, data: bytes) -> bool: - """ - Sends all data at once over the socket. - - Parameters - ---------- - data: bytes - - Returns - ------- - bool: If the data was sent successfully. - """ - try: - self.__socket.sendall(data) - except socket.error as e: - print(f"Could not send data: {e}.") - return False - - return True - - def recv(self, buf_size: int) -> "tuple[bool, bytes | None]": - """ - Reads buf_size bytes from the socket. - - Parameters - ---------- - buf_size: int - The number of bytes to receive. - - Returns - ------- - tuple[bool, Socket | None] - The first parameter represents if the read is successful. - - If it is not successful, the second parameter will be None. - - If it is successful, the second parameter will be the data - that is read. - """ - try: - data = self.__socket.recv(buf_size) - except socket.error as e: - print(f"Could not receive data: {e}.") - return False, None - - return True, data - - def close(self) -> bool: - """ - Closes the socket object. All future operations on the socket object will fail. - - Returns - ------- - bool: If the socket was closed successfully. - """ - try: - self.__socket.close() - except socket.error as e: - print(f"Could not close socket: {e}.") - return False - - return True - - def address(self) -> "tuple[str, int]": - """ - Retrieves the address that the socket is listening on. - - Returns - ------- - tuple[str, int] - The address in the format (ip address, port). - """ - return self.__socket.getsockname() - - def get_socket(self) -> socket.socket: - """ - Getter for the underlying socket objet. - """ - return self.__socket diff --git a/network/test_receiver.py b/network/test_receiver.py deleted file mode 100644 index b35d99a..0000000 --- a/network/test_receiver.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Test socket operations by receiving images over server sockets. -""" - -import struct - -from network.modules.server_socket import ClientSocket, ServerSocket - - -SOCKET_ADDRESS = "127.0.0.1" -SOCKET_PORT = 8080 - - -def recv_all(client_socket: ClientSocket, data_len: int) -> "tuple[bool, bytes | None]": - """ - Receives an image of data_len bytes from a socket - """ - image_data = b"" - data_recv = 0 - while data_recv < data_len: - result, data = client_socket.recv(data_len - data_recv) - if not result: - return False, None - - print(f"Received {len(data)} bytes.") - data_recv += len(data) - image_data += data - - return True, image_data - - -def start_server(host: str, port: int) -> int: - """ - Starts server listening on host:port that receives images and sends them back to the client. - """ - result, server_socket = ServerSocket.create( # pylint: disable=unpacking-non-sequence - host=host, port=port - ) - if not result: - return -1 - - print(f"Listening on {host}:{port}.") - - result, client_socket = server_socket.accept() - if not result: - return -2 - - if client_socket.address() != (host, port): - return -3 - - print(f"Accepted connection from {host}:{port}.") - - while True: - # Length of image in bytes - result, data_len = client_socket.recv(4) - if not result: - return -4 - - if not data_len: - break - - print("Received image byte length from client.") - data_len = struct.unpack(" int: + """ + Starts server listening on host:port that receives images and sends them back to the client. + """ + result, server_socket = TcpServerSocket.create(host=host, port=port) + assert result, "Server cration failed." + + while True: + result, data_len = server_socket.recv(4) + if not result: + print("Client closed the connection.") + break + + print("Received image byte length from client.") + + data_len = struct.unpack(" "tuple[bool, bytes | None]": return True, encoded_image_bytes -def recv_all(client_socket: ClientSocket, data_len: int) -> "tuple[bool, bytes | None]": - """ - Receives an image of data_len bytes from a socket - """ - image_data = b"" - data_recv = 0 - while data_recv < data_len: - result, data = client_socket.recv(data_len - data_recv) - if not result: - return False, None - - print(f"Received {len(data)} bytes.") - data_recv += len(data) - image_data += data - - return True, image_data - - def start_sender(host: str, port: int) -> int: """ Client will send landing pad images to the server, and the server will send them back. """ - result, client_socket = ClientSocket.create( # pylint: disable=unpacking-non-sequence - host=host, port=port - ) + result, client_socket = TcpClientSocket.create(host=host, port=port) assert result, "Failed to create ClientSocket." print(f"Connected to: {host}:{port}.") @@ -81,7 +61,7 @@ def start_sender(host: str, port: int) -> int: assert result, "Failed to send image data." print("Sent image data to server.") - result, image_data = recv_all(client_socket, len(image)) + result, image_data = client_socket.recv(len(image)) assert result, "Failed to receive returning image data." print("Received image data from server.") assert image == image_data, "Sent image bytes does not match received image bytes"