feat(udp): get command finished

- Files size should be around 750 bytes or less
- Regenerate test files in docker and in `client_directory` & `server_directory` folders
- Abstract the client and server: UDPClient -> Client
This commit is contained in:
minhtrannhat 2023-12-06 17:30:12 -05:00
parent 5519be9222
commit dcd8c1a1a5
Signed by: minhtrannhat
GPG Key ID: E13CFA85C53F8062
9 changed files with 318 additions and 207 deletions

View File

@ -9,8 +9,10 @@ WORKDIR /client
# Copy the local client to the container # Copy the local client to the container
RUN mkdir /client_directory RUN mkdir /client_directory
RUN dd if=/dev/urandom of=/client_directory/file_local.txt bs=1024 count=10
RUN dd if=/dev/urandom of=/client_directory/image_local.png bs=1024 count=50 # these files is only 750 bytes
RUN dd if=/dev/urandom of=/client_directory/file_local.txt bs=1 count=750
RUN dd if=/dev/urandom of=/client_directory/image_local.png bs=1 count=750
# Start your Python application # Start your Python application
#CMD python client.py --debug 1 #CMD python client.py --debug 1

View File

@ -9,8 +9,9 @@ WORKDIR /server
# Create files with random content in the /server directory # Create files with random content in the /server directory
RUN mkdir /server_directory RUN mkdir /server_directory
RUN dd if=/dev/urandom of=/server_directory/file_server.txt bs=1024 count=10 # these files is only 750 bytes
RUN dd if=/dev/urandom of=/server_directory/image_server.png bs=1024 count=50 RUN dd if=/dev/urandom of=/server_directory/file_server.txt bs=1 count=750
RUN dd if=/dev/urandom of=/server_directory/image_server.png bs=1 count=750
# Start your Python application # Start your Python application
#CMD python server.py --port_number 12000 --debug 1 #CMD python server.py --port_number 12000 --debug 1

View File

@ -12,7 +12,7 @@ You can run `python3 src/myftp/client.py --directory <insert valid directory tha
### Server ### Server
By default, the server IP address or hostname or server name will be `0.0.0.0` (meaning it will bind to all interfaces). The `--port_number` flag, if not specified will be by default `12000`. By default, the server IP address or hostname or server name will be `0.0.0.0` or `localhost` (meaning it will bind to all interfaces). The `--port_number` flag, if not specified will be by default `12000`.
You can run `python3 src/myftp/server.py --directory <insert valid directory that you have read/write permissions>` to start the server or `python3 src/myftp/server.py --ip_addr <insert ip addr of the server> --port_number <insert port number here> --debug 1 --directory <insert valid directory that you have read/write permissions>` for debugging purposes and to specify the port number. You can run `python3 src/myftp/server.py --directory <insert valid directory that you have read/write permissions>` to start the server or `python3 src/myftp/server.py --ip_addr <insert ip addr of the server> --port_number <insert port number here> --debug 1 --directory <insert valid directory that you have read/write permissions>` for debugging purposes and to specify the port number.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,8 +6,8 @@
from socket import socket, AF_INET, SOCK_DGRAM from socket import socket, AF_INET, SOCK_DGRAM
from typing import Pattern, Tuple from typing import Pattern, Tuple
from argparse import ArgumentParser from argparse import ArgumentParser
import traceback
import os import os
import pickle
import re import re
@ -19,104 +19,119 @@ summary_command_pattern: Pattern = re.compile(r"^summary\s+[^\s]+$")
change_command_pattern: Pattern = re.compile(r"^change\s+[^\s]+\s+[^\s]+$") change_command_pattern: Pattern = re.compile(r"^change\s+[^\s]+\s+[^\s]+$")
# opcodes # opcodes
put_request_opcode:str = "000" put_request_opcode: int = 0b000
get_request_opcode:str = "001" get_request_opcode: int = 0b001
change_request_opcode: str = "010" change_request_opcode: int = 0b010
summary_request_opcode: str = "011" summary_request_opcode: int = 0b011
help_requrest_opcode: str = "100" help_request_opcode: int = 0b100
# Res-code dict # Res-code dict
rescode_dict: dict[str, str] = { rescode_dict: dict[int, str] = {
"000": "Put/Change Request Successful", 0b011: "File Not Found Error",
"001": "Get Request Successful", 0b100: "Unknown Request",
"010": "Summary Request Successful", 0b101: "Change Unsuccessful Error",
"011": "File Not Found Error", 0b000: "Put/Change Request Successful",
"100": "Unknown Request", 0b001: "Get Request Successful",
"101": "Change Unsuccessful Error", 0b010: "Summary Request Successful",
"110": "Help" 0b110: "Help",
} }
# custome type to represent the hostname(server name) and the server port # custome type to represent the hostname(server name) and the server port
Address = Tuple[str, int] Address = Tuple[str, int]
class UDPClient: class Client:
def __init__(self, server_name: str, server_port: int, debug: bool): def __init__(
self,
server_name: str,
server_port: int,
directory_path: str,
debug: bool,
protocol: str,
):
self.server_name: str = server_name self.server_name: str = server_name
self.server_port: int = server_port self.server_port: int = server_port
self.mode: str = "UDP" self.protocol: str = protocol
self.pong_received: bool = False self.directory_path = directory_path
self.debug = debug self.debug = debug
def run(self): def run(self):
# server cannot be reached, stop the client immediately
if not self.pong_received:
return
client_socket = socket(AF_INET, SOCK_DGRAM) client_socket = socket(AF_INET, SOCK_DGRAM)
client_socket.settimeout(10)
try: try:
while True: while True:
# get command from user # get command from user
command = input(f"myftp> - {self.mode} - : ").strip().lower() command = input(f"myftp> - {self.protocol} - : ").strip().lower()
# handling the "bye" command # handling the "bye" command
if command == "bye": if command == "bye":
client_socket.close() client_socket.close()
print(f"myftp> - {self.mode} - Session is terminated") print(f"myftp> - {self.protocol} - Session is terminated")
break break
# list files available on the server
elif command == "list":
self.get_files_list_from_server(client_socket)
continue
# help # help
elif command == "help": elif command == "help":
request_payload: str = help_requrest_opcode + "00000" # 10000000 first_byte: int = help_request_opcode << 5
command_name = "help"
print( print(
f"myftp> - {self.mode} - Asking for help from the server" f"myftp> - {self.protocol} - Asking for help from the server"
) if self.debug else None ) if self.debug else None
# get command handling # get command handling
elif get_command_pattern.match(command): elif get_command_pattern.match(command):
_, filename = command.split(" ", 1) command_name, filename = command.split(" ", 1)
first_byte = (get_request_opcode << 5) + len(filename)
second_byte_to_n_byte: bytes = filename.encode("ascii")
print( print(
f"myftp> - {self.mode} - : Getting file {filename} from the server" f"myftp> - {self.protocol} - Getting file {filename} from the server"
) if self.debug else None ) if self.debug else None
# put command handling # put command handling
elif put_command_pattern.match(command): elif put_command_pattern.match(command):
_, filename = command.split(" ", 1) _, filename = command.split(" ", 1)
print( print(
f"myftp> - {self.mode} - : Putting file {filename} into the server" f"myftp> - {self.protocol} - Putting file {filename} into the server"
) if self.debug else None ) if self.debug else None
# summary command handling # summary command handling
elif summary_command_pattern.match(command): elif summary_command_pattern.match(command):
_, filename = command.split(" ", 1) _, filename = command.split(" ", 1)
print( print(
f"myftp> - {self.mode} - : Summary file {filename} from the server" f"myftp> - {self.protocol} - Summary file {filename} from the server"
) if self.debug else None ) if self.debug else None
# change command handling # change command handling
elif change_command_pattern.match(command): elif change_command_pattern.match(command):
_, old_filename, new_filename = command.split() _, old_filename, new_filename = command.split()
print( print(
f"myftp> - {self.mode} - : Changing file named {old_filename} into {new_filename} on the server" f"myftp> - {self.protocol} - Changing file named {old_filename} into {new_filename} on the server"
) if self.debug else None ) if self.debug else None
else: else:
print( print(
f"myftp> - {self.mode} - : Invalid command. Supported commands are put, get, summary, change, list and help. Type help for detailed usage." f"myftp> - {self.protocol} - Invalid command. Supported commands are put, get, summary, change, list and help. Type help for detailed usage."
) )
continue continue
# convert the payload to bytes so it can be sent to the server # get or put case
byte_representation_req_payload: bytes = bytes([int(request_payload, 2)]) if command_name == "get" or command_name == "put":
payload = first_byte.to_bytes(1, "big") + second_byte_to_n_byte
client_socket.sendto(byte_representation_req_payload, (self.server_name, self.server_port)) # help case
else:
payload: bytes = first_byte.to_bytes(1, "big")
print(
f"myftp> - {self.protocol} - sent payload {bin(int.from_bytes(payload, byteorder='big'))[2:]} to the server"
) if self.debug else None
client_socket.sendto(payload, (self.server_name, self.server_port))
response_payload = client_socket.recv(2048) response_payload = client_socket.recv(2048)
@ -124,83 +139,86 @@ class UDPClient:
except ConnectionRefusedError: except ConnectionRefusedError:
print( print(
f"myftp> - {self.mode} - ConnectionRefusedError happened. Please restart the client program, make sure the server is running and/or put a different server name and server port." f"myftp> - {self.protocol} - ConnectionRefusedError happened. Please restart the client program, make sure the server is running and/or put a different server name and server port."
) )
except Exception as error:
print(
f"myftp> - {self.mode} - {error} happened."
)
finally:
client_socket.close()
# ping pong UDP
def check_udp_server(self):
# Create a UDP socket
client_socket = socket(AF_INET, SOCK_DGRAM)
# will time out after 5 seconds
client_socket.settimeout(5)
try:
# Send a test message to the server
message = b"ping"
client_socket.sendto(message, (self.server_name, self.server_port))
# Receive the response
data, _ = client_socket.recvfrom(1024)
# If the server responds, consider the address valid
print(
f"myftp> - {self.mode} - Server at {self.server_name}:{self.server_port} is valid. Response received: {data.decode('utf-8')}"
)
# code reached here meaning no problem with the connection
self.pong_received = True
except TimeoutError: except TimeoutError:
# Server did not respond within the specified timeout # Server did not respond within the specified timeout
print( print(
f"myftp> - {self.mode} - Server at {self.server_name} did not respond within 5 seconds. Check the address or server status." f"myftp> - {self.protocol} - Server at {self.server_name} did not respond within 5 seconds. Check the address or server status."
) )
except Exception as error:
traceback_info = traceback.format_exc()
print(f"myftp> - {self.protocol} - {error} happened.")
print(traceback_info)
finally: finally:
# Close the socket
client_socket.close() client_socket.close()
# get list of files currently on the server def parse_response_payload(self, response_payload: bytes):
def get_files_list_from_server(self, client_socket: socket) -> list[str]: first_byte = bytes([response_payload[0]])
client_socket.send("list".encode()) first_byte_binary = int.from_bytes(first_byte, "big")
encoded_message, server_address = client_socket.recvfrom(4096) rescode = first_byte_binary >> 5
file_list = pickle.loads(encoded_message) filename_length = first_byte_binary & 0b00011111
print(f"Received file list from {server_address}: {file_list}") response_data = response_payload[1:]
client_socket.close() response_data_length = len(response_data)
return file_list
def parse_response_payload(self,
response_payload: bytes):
# we want to get the first byte as a string i.e "01010010"
first_byte: str = bin(response_payload[0])[2:].zfill(8)
rescode: str = first_byte[:3]
response_data_length: int = int(first_byte[-5:], 2)
response_data: bytes = response_payload[1:]
print( print(
f"myftp> - {self.mode} - First_byte from server response: {first_byte}. Rescode: {rescode}. Data length: {response_data_length}" f"myftp> - {self.protocol} - First_byte from server response: {first_byte}. Rescode: {rescode}. File name length: {filename_length}. Data length: {response_data_length}"
) if self.debug else None ) if self.debug else None
try: try:
print( print(
f"myftp> - {self.mode} - Res-code meaning: {rescode_dict[rescode]}" f"myftp> - {self.protocol} - Res-code meaning: {rescode_dict[rescode]}"
) if self.debug else None ) if self.debug else None
except KeyError: except KeyError:
print(f"myftp> - {self.protocol} - Res-code does not have meaning")
# error rescodes
if rescode in [0b011, 0b100, 0b101]:
print(f"myftp> - {self.protocol} - {rescode_dict[rescode]}")
# successful rescodes
else:
if rescode == 0b110:
print(f"myftp> - {self.protocol} - {response_data.decode('ascii')}")
else:
self.handle_get_response_from_server(filename_length, response_data)
def handle_get_response_from_server(
self, filename_length: int, response_data: bytes
):
"""
response_data is
file name (filename_length bytes) +
file size (4 bytes) +
file content (rest of the bytes)
"""
try:
filename = response_data[:filename_length].decode("ascii")
file_size = int.from_bytes(
response_data[filename_length : filename_length + 4], "big"
)
file_content = response_data[
filename_length + 4 : filename_length + 4 + file_size
]
print( print(
f"myftp> - {self.mode} - Res-code does not have meaning" f"myftp> - {self.protocol} - Filename: {filename}, File_size: {file_size} bytes"
) )
print( with open(os.path.join(self.directory_path, filename), "wb") as file:
f"myftp> - {self.mode} - {response_data.decode()}" file.write(file_content)
)
print(
f"myftp> - {self.protocol} - File {filename} has been downloaded successfully"
)
except Exception:
raise
def get_address_input() -> Address: def get_address_input() -> Address:
@ -223,9 +241,9 @@ def get_address_input() -> Address:
# Valid tuple, return it # Valid tuple, return it
return address return address
except ValueError as e: except ValueError:
print( print(
f"Error: Invalid input. Please enter a servername/hostname/ip address as a string and the port number as an integer separated by a space." "Error: Invalid input. Please enter a servername/hostname/ip address as a string and the port number as an integer separated by a space."
) )
@ -272,20 +290,29 @@ def init():
) )
return return
user_supplied_address = get_address_input()
# UDP client selected here # UDP client selected here
if protocol_selection == "2": if protocol_selection == "2":
user_supplied_address = get_address_input() udp_client = Client(
user_supplied_address[0],
udp_client = UDPClient( user_supplied_address[1],
user_supplied_address[0], user_supplied_address[1], args.debug args.directory,
args.debug,
"UDP",
) )
udp_client.check_udp_server()
udp_client.run() udp_client.run()
else: else:
# tcp client here tcp_client = Client(
pass user_supplied_address[0],
user_supplied_address[1],
args.directory,
args.debug,
"TCP",
)
tcp_client.run()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -5,33 +5,45 @@
from socket import socket, AF_INET, SOCK_DGRAM from socket import socket, AF_INET, SOCK_DGRAM
from argparse import ArgumentParser from argparse import ArgumentParser
from typing import Optional, Tuple
import os import os
import pickle
# Res-code # Res-codes
correct_put_and_change_request_rescode: str = "000" rescode_success_dict: dict[str, int] = {
correct_get_request_rescode: str = "001" "correct_put_and_change_request_rescode": 0b000,
correct_summary_request_rescode: str = "010" "correct_get_request_rescode": 0b001,
file_not_error_rescode: str = "011" "correct_summary_request_rescode": 0b010,
unknown_request_rescode: str = "100" "help_rescode": 0b110,
unsuccessful_change_rescode: str = "101" }
help_rescode: str = "110"
rescode_fail_dict: dict[str, int] = {
"file_not_error_rescode": 0b011,
"unknown_request_rescode": 0b100,
"unsuccessful_change_rescode": 0b101,
}
# opcodes # opcodes
put_request_opcode: str = "000" op_codes_dict: dict[int, str] = {
get_request_opcode: str = "001" 0b000: "put",
change_request_opcode: str = "010" 0b001: "get",
summary_request_opcode: str = "011" 0b010: "change",
help_requrest_opcode: str = "100" 0b011: "summary",
0b100: "help",
}
class UDPServer: class Server:
def __init__( def __init__(
self, server_name: str, server_port: int, directory_path: str, debug: bool self,
server_name: str,
server_port: int,
directory_path: str,
debug: bool,
protocol: str,
) -> None: ) -> None:
self.server_name = server_name self.server_name = server_name
self.server_port = server_port self.server_port = server_port
self.mode: str = "UDP" self.protocol: str = protocol
self.directory_path = directory_path self.directory_path = directory_path
self.debug = debug self.debug = debug
@ -40,122 +52,188 @@ class UDPServer:
self.server_socket.bind((self.server_name, self.server_port)) self.server_socket.bind((self.server_name, self.server_port))
print( print(
f"myftp> - {self.mode} - Server is ready to receive at {self.server_name}:{self.server_port}" f"myftp> - {self.protocol} - Server is ready to receive at {self.server_name}:{self.server_port}"
) if self.debug else None ) if self.debug else None
shut_down = False shut_down = False
try: try:
while not shut_down: while not shut_down:
message, clientAddress = self.server_socket.recvfrom(2048)
# decode for quick and dirty commands like ping and list server files
# outside of the scope of the project
try:
request_payload = message.decode()
except UnicodeDecodeError:
# most commands (get, put, summary ...) will be handled by this catch block
request_payload: str = bin(int.from_bytes(message, byteorder='big'))[2:]
print( print(
f"myftp> - {self.mode} ------------------------------------------------------------------" f"myftp> - {self.protocol} ------------------------------------------------------------------"
) if self.debug else None ) if self.debug else None
req_payload, clientAddress = self.server_socket.recvfrom(2048)
first_byte = bytes([req_payload[0]])
request_type, filename_length = self.decode_first_byte(first_byte)
print( print(
f"myftp> - {self.mode} - Received message from client at {clientAddress}: {request_payload}" f"myftp> - {self.protocol} - Received message from client at {clientAddress}: {req_payload}"
) if self.debug else None ) if self.debug else None
# check for connectivity
if request_payload == "ping":
self.server_socket.sendto("pong".encode(), clientAddress)
print(
f"myftp> - {self.mode} - pong sent back to client"
) if self.debug else None
continue
# list files available on server
elif request_payload == "list":
encoded_message = pickle.dumps(
get_files_in_directory(self.directory_path)
)
self.server_socket.sendto(encoded_message, clientAddress)
continue
# help request handling # help request handling
elif request_payload == help_requrest_opcode + "00000": if request_type == "help":
print( print(
f"myftp> - {self.mode} - Client message parsed. Received help request" f"myftp> - {self.protocol} - Client message parsed. Received help request"
) if self.debug else None ) if self.debug else None
rescode = help_rescode rescode = rescode_success_dict["help_rescode"]
response_data_string = "get,put,summary,change,help,bye" response_data = "get,put,summary,change,help,bye".encode("ascii")
filename = None
filename_length = None
else: elif request_type == "get":
# handle unrecognized request here pre_payload = self.process_get_req(req_payload[1:])
pass
payload: bytes = self.build_res_payload(rescode, response_data_string) if (
pre_payload[0] is not None
and pre_payload[1] is not None
and pre_payload[2] is not None
):
rescode = rescode_success_dict["correct_get_request_rescode"]
filename = pre_payload[0]
filename_length = pre_payload[2]
response_data = pre_payload[1]
self.server_socket.sendto(payload, clientAddress) else:
rescode = rescode_fail_dict["file_not_error_rescode"]
filename_length = None
filename = None
response_data = None
res_payload: bytes = self.build_res_payload(
rescode=rescode, # type: ignore
filename_length=filename_length,
filename=filename, # type: ignore
response_data=response_data, # type:ignore
)
self.server_socket.sendto(res_payload, clientAddress)
print( print(
f"myftp> - {self.mode} - Sent message to client at {clientAddress}: {payload}" f"myftp> - {self.protocol} - Sent message to client at {clientAddress}: {res_payload}"
) if self.debug else None ) if self.debug else None
except KeyboardInterrupt: except KeyboardInterrupt:
shut_down = True shut_down = True
self.server_socket.close() self.server_socket.close()
print(f"myftp> - {self.mode} - Server shutting down") print(f"myftp> - {self.protocol} - Server shutting down")
finally: finally:
print(f"myftp> - {self.mode} - Closed the server socket") print(f"myftp> - {self.protocol} - Closed the server socket")
def decode_first_byte(self, first_byte: bytes) -> Tuple[str, int]:
"""
Retrieve the request_type from first byte
"""
if len(first_byte) != 1:
raise ValueError("Input is not 1 byte")
first_byte_to_binary = int.from_bytes(first_byte, "big")
try:
request_type = op_codes_dict[first_byte_to_binary >> 5]
filename_length_in_bytes = first_byte_to_binary & 0b00011111
print(
f"myftp> - {self.protocol} - First byte parsed. Request type: {request_type}. Filename length in bytes: {filename_length_in_bytes}"
)
except KeyError:
raise KeyError("Can't find the request type of this payload")
return request_type, filename_length_in_bytes
def process_get_req(
self, second_byte_to_byte_n: bytes
) -> Tuple[Optional[str], Optional[bytes], Optional[int]]:
"""
Process the get request
If successful, return the filename, content and the content_length
If not, return None, None, None tuple
"""
filename = second_byte_to_byte_n.decode("ascii")
try:
with open(os.path.join(self.directory_path, filename), "rb") as file:
content = file.read()
content_length = len(content)
return filename, content, content_length
except FileNotFoundError:
print(f"myftp> - {self.protocol} - file {filename} not found")
return (None, None, None)
# assembling the payload to send back to the client # assembling the payload to send back to the client
def build_res_payload(self, def build_res_payload(
rescode: str, self,
response_data_string: str) -> bytes: rescode: int,
filename_length: Optional[int] = None,
filename: Optional[str] = None,
response_data: Optional[bytes] = None,
) -> bytes:
print(
f"myftp> - {self.protocol} - Assembling response payload to be sent back to the client"
)
print(f"myftp> - {self.mode} - Assembling response payload to be sent back to the client") data_len = len(response_data) if response_data is not None else None
bytes_response_data = response_data_string.encode("utf-8") print(
f"myftp> - {self.protocol} - Rescode {format(rescode, '03b')}"
) if self.debug else None
data_len = len(bytes_response_data) print(
f"myftp> - {self.protocol} - Length of data {data_len}"
) if self.debug else None
print(f"myftp> - {self.mode} - Rescode {rescode}") if self.debug else None print(
print(f"myftp> - {self.mode} - Length of data {data_len}") if self.debug else None f"myftp> - {self.protocol} - Data {response_data}"
print(f"myftp> - {self.mode} - Data {response_data_string}") if self.debug else None ) if self.debug else None
# convert to binary # convert to binary
try: try:
# pad the length of data to make sure it is always 5 bits # get case
# i.e "010" -> "00010" if filename is not None:
binary_data_len: str = bin(data_len).zfill(5) first_byte = ((rescode << 5) + len(filename)).to_bytes(1, "big")
# help case
elif filename is None and response_data is not None:
first_byte = ((rescode << 5) + len(response_data)).to_bytes(1, "big")
# unsuccessful cases
else:
first_byte = (rescode << 5).to_bytes(1, "big")
print(f"myftp> - {self.mode} - binary_data_len {binary_data_len[2:]}") if self.debug else None # we only need the firstbyte
if filename is None:
second_byte_to_FL_plus_five = None
else:
second_byte_to_FL_plus_five = (
filename.encode() + len(response_data).to_bytes(4, "big")
if response_data is not None
else None
)
# create the first byte print(
# since binary_data_len is of the format 0b00100, we have to remove the first two characters 0b f"myftp> - {self.protocol} - First byte assembled for rescode {format(rescode, '03b')}: {bin(int.from_bytes(first_byte, byteorder='big'))[2:]}"
first_byte: bytes = bytes([int(rescode + binary_data_len[2:], 2)]) ) if self.debug else None
print(f"myftp> - {self.mode} - First byte assembled for rescode {rescode}: {bin(int.from_bytes(first_byte, byteorder='big'))[2:]}") if self.debug else None if second_byte_to_FL_plus_five is not None and response_data is not None:
res_payload = first_byte + second_byte_to_FL_plus_five + response_data
# help case
elif second_byte_to_FL_plus_five is None and response_data is not None:
res_payload = first_byte + response_data
else:
res_payload = first_byte
except Exception as e: return res_payload
raise Exception(e)
res_payload = first_byte + bytes_response_data except Exception:
raise
return res_payload
def get_files_in_directory(directory_path: str) -> list[str]:
file_list = []
for _, _, files in os.walk(directory_path):
for file in files:
file_list.append(file)
return file_list
def check_directory(path: str) -> bool: def check_directory(path: str) -> bool:
@ -218,15 +296,18 @@ def init():
# UDP client selected here # UDP client selected here
if protocol_selection == "2": if protocol_selection == "2":
udp_server = UDPServer( udp_server = Server(
args.ip_addr, args.port_number, args.directory, args.debug args.ip_addr, args.port_number, args.directory, args.debug, "UDP"
) )
udp_server.run() udp_server.run()
else: else:
# tcp client here tcp_server = Server(
pass args.ip_addr, args.port_number, args.directory, args.debug, "TCP"
)
tcp_server.run()
if __name__ == "__main__": if __name__ == "__main__":