-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Created UDP Sockets * Addressed Comments * Added send() function and override recv() function in client_socket.py, addressed minor changes in socket_wrapper.py * Made requested changes * Updated docstrings
- Loading branch information
Showing
3 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import socket | ||
from network.modules.UDP.socket_wrapper import UdpSocket | ||
|
||
|
||
class UdpClientSocket(UdpSocket): | ||
""" | ||
Wrapper for client socket operations | ||
""" | ||
|
||
__create_key = object() | ||
|
||
def __init__( | ||
self, | ||
class_private_create_key: object, | ||
socket_instance: socket.socket, | ||
server_address: tuple, | ||
) -> None: | ||
""" | ||
Private Constructor, use create() method. | ||
""" | ||
assert class_private_create_key is UdpClientSocket.__create_key | ||
self.__socket = socket_instance | ||
self.server_address = server_address | ||
|
||
@classmethod | ||
def create( | ||
cls, host: str = "localhost", port: int = 5000, connection_timeout: float = 10.0 | ||
) -> "tuple[bool, UdpClientSocket | None]": | ||
""" | ||
Initializes UDP client socket with the appropriate server address. | ||
Parameters | ||
---------- | ||
host: str, default "localhost" | ||
The hostname of the server. | ||
port: int, default 5000 | ||
The port number of the server. | ||
connection_timeout: float, default 10.0 | ||
Timeout for establishing connection, in seconds | ||
Returns | ||
------- | ||
tuple[bool, UdpClientSocket | None] | ||
The boolean value represents whether the initialization was successful or not. | ||
- If it is not successful, the second parameter will be None. | ||
- If it is successful, the method will return True and a UdpClientSocket object will be created. | ||
""" | ||
|
||
if connection_timeout <= 0: | ||
print("Must provide positive non-zero value.") | ||
return False, None | ||
|
||
try: | ||
socket_instance = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
socket_instance.settimeout(connection_timeout) | ||
server_address = (host, port) | ||
return True, UdpClientSocket(cls.__create_key, socket_instance, server_address) | ||
|
||
except TimeoutError as e: | ||
print(f"Connection timed out: {e}") | ||
|
||
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: {e}") | ||
|
||
return False, None | ||
|
||
def send(self, data: bytes) -> bool: | ||
""" | ||
Sends data to the specified server address | ||
Parameters | ||
---------- | ||
data: bytes | ||
Takes in raw data that we wish to send | ||
Returns | ||
------- | ||
bool: True if data is sent successfully, and false if it fails to send | ||
""" | ||
|
||
try: | ||
host, port = self.server_address | ||
super().send_to(data, host, port) | ||
except socket.error as e: | ||
print(f"Could not send data: {e}") | ||
return False | ||
|
||
return True | ||
|
||
def recv(self, buf_size: int) -> None: | ||
|
||
""" | ||
Receive data method override to prevent client sockets from receiving data. | ||
Parameters | ||
---------- | ||
bufsize: int | ||
The maximum amount of data to be received at once. | ||
Raises | ||
------ | ||
NotImplementedError | ||
Always raised because client sockets should not receive data. | ||
""" | ||
|
||
raise NotImplementedError("Client sockets cannot receive data as they are not bound by a port.") | ||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import socket | ||
from network.modules.UDP.socket_wrapper import UdpSocket | ||
|
||
|
||
class UdpServerSocket(UdpSocket): | ||
""" | ||
Wrapper for server socket operations. | ||
""" | ||
|
||
__create_key = object() | ||
|
||
def __init__( | ||
self, | ||
class_private_create_key: object, | ||
socket_instance: socket.socket, | ||
server_address: tuple, | ||
) -> None: | ||
""" | ||
Private Constructor, use create() method. | ||
""" | ||
assert class_private_create_key is UdpServerSocket.__create_key, "Use create() method" | ||
super().__init__(socket_instance=socket_instance) | ||
self.__socket = socket_instance | ||
self.server_address = server_address | ||
|
||
@classmethod | ||
def create(cls, host: str = "", port: int = 5000, connection_timeout: float = 10.0) -> "tuple[bool, UdpServerSocket | None]": | ||
""" | ||
Creates a UDP server socket bound to the provided host and port. | ||
Parameters | ||
---------- | ||
host: str (default "") | ||
The hostname or IP address to bind the socket to. | ||
port: int (default 5000) | ||
The port number to bind the socket to. | ||
connection_timeout: float (default 10.0) | ||
Timeout for establishing connection, in seconds | ||
Returns | ||
------- | ||
tuple[bool, UdpServerSocket | 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 | ||
UdpServerSocket object. | ||
""" | ||
|
||
if connection_timeout <= 0: | ||
print(f"Must provide a positive non-zero value.") | ||
return False, None | ||
|
||
try: | ||
socket_instance = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
socket_instance.settimeout(connection_timeout) | ||
server_address = (host, port) | ||
socket_instance.bind(server_address) | ||
return True, UdpServerSocket(cls.__create_key, socket_instance, server_address) | ||
|
||
except TimeoutError as e: | ||
print(f"Connection timed out.") | ||
except socket.error as e: | ||
print(f"Could not create socket, error: {e}.") | ||
return False, None | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import socket | ||
import struct | ||
|
||
CHUNK_SIZE = 4096 | ||
|
||
class UdpSocket: | ||
""" | ||
Wrapper for Python's socket module. | ||
""" | ||
|
||
|
||
|
||
def __init__(self, socket_instance: socket.socket = None) -> None: | ||
""" | ||
Parameters | ||
---------- | ||
instance: socket.socket | ||
For initializing Socket with an existing socket object. | ||
""" | ||
|
||
if socket_instance is None: | ||
self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
self.__socket.settimeout(10.0) | ||
else: | ||
self.__socket = socket_instance | ||
|
||
@classmethod | ||
def send_to(self, data: bytes, host: str = "", port: int = 5000) -> bool: | ||
""" | ||
Sends data to specified address | ||
Parameters | ||
---------- | ||
data: bytes | ||
host: str (default "") | ||
Empty string is interpreted as '0.0.0.0' (IPv4) or '::' (IPv6), which is an open address | ||
port: int (default 5000) | ||
The host, combined with the port, will form the address as a tuple | ||
Returns | ||
------- | ||
bool: if data was transferred successfully | ||
""" | ||
address = (host, port) | ||
|
||
data_sent = 0 | ||
data_size = len(data) | ||
|
||
while data_sent < data_size: | ||
|
||
if data_sent + CHUNK_SIZE > data_size: | ||
chunk = data[data_sent:data_size] | ||
else: | ||
chunk = data[data_sent:data_sent+CHUNK_SIZE] | ||
|
||
try: | ||
self.__socket.sendto(chunk, address) | ||
data_sent += len(chunk) | ||
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]": | ||
""" | ||
Parameters | ||
---------- | ||
buf_size: int | ||
The number of bytes to receive | ||
Returns | ||
------- | ||
tuple: | ||
bool - True if data was received and unpacked successfully, False otherwise | ||
bytes | None - The received data, or None if unsuccessful | ||
""" | ||
data = b'' | ||
addr = None | ||
data_size = 0 | ||
|
||
while data_size < buf_size: | ||
|
||
try: | ||
packet, current_addr = self.__socket.recvfrom(buf_size) | ||
if addr is None: | ||
addr = current_addr | ||
elif addr != current_addr: | ||
print(f"Data received from multiple addresses: {addr} and {current_addr}") | ||
packet = b'' | ||
|
||
# Add the received packet to the accumulated data and increment the size accordingly | ||
data += packet | ||
data_size += len(packet) | ||
|
||
|
||
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 False | ||
|
||
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 |