Last active
June 27, 2023 06:20
-
-
Save a-sajjad72/048f2b10569e2d1f4e50b6fd82a7c903 to your computer and use it in GitHub Desktop.
a simple framework based on client-server model to send/receive file/messages
This file contains hidden or 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
import socket | |
from _thread import * | |
import threading | |
import os, zipfile | |
from pip._vendor.rich.console import Console | |
from pip._vendor.rich.progress import ( | |
Progress, | |
TextColumn, | |
BarColumn, | |
TransferSpeedColumn, | |
DownloadColumn, | |
TimeElapsedColumn, | |
) # for progress bar | |
class FileTransfer: | |
""" | |
A class for handling file transfer between client and server. | |
Args: | |
conn : Socket for the connection to send/receive data. | |
bufferSize (int, optional): The buffer size for sending/receiving data. Defaults to 4096 B (4 KiB) | |
""" | |
def __init__(self, conn, bufferSize: int = 4096): | |
self.conn = conn | |
self.bufferSize = bufferSize | |
def recv_file(self, path: str = os.getcwd()) -> str: | |
""" | |
Receives the file from the initialized connection. | |
Args: path (str, optional): The path of the directory to store received file. Defaults to current working directory. | |
Returns: | |
str: Name of the received file. | |
""" | |
try: | |
filename, filesize = self.recv_msg().split(";") | |
filesize = int(filesize) | |
console = Console() | |
with Progress( | |
TextColumn("[progress.description]{task.description}"), | |
BarColumn(), | |
DownloadColumn(binary_units=True), | |
TextColumn("at"), | |
TransferSpeedColumn(), | |
TextColumn("in"), | |
TimeElapsedColumn(), | |
transient=True, | |
) as progress: | |
console.print(filename, style="bold") | |
recv_task = progress.add_task("Receiving", total=filesize) | |
bytes_received = 0 | |
with open(os.path.join(path, filename), "wb") as f: | |
while bytes_received < filesize: | |
data = self.conn.recv( | |
min(self.bufferSize, filesize - bytes_received) | |
) | |
if not data: | |
break | |
elif data: | |
bytes_received += f.write(data) | |
progress.update(recv_task, completed=bytes_received) | |
self.send_msg(str(bytes_received)) | |
result = bool(self.recv_msg()) | |
if result: | |
console.print(f"{filename} successfully received", style="bold green") | |
return filename | |
except Exception as e: | |
return f"ERROR while receiving: {e}" | |
def send_file(self, filePath: str) -> None: | |
""" | |
Sends the file to the initialized connection. | |
Args: | |
filePath (str): path/name of the file to send. | |
""" | |
try: | |
filename = os.path.basename( | |
filePath.rstrip("\\" if os.name == "nt" else "/") | |
) | |
filesize = os.path.getsize(filePath) | |
self.send_msg(";".join([filename, str(filesize)])) | |
console = Console() | |
with Progress( | |
TextColumn("[progress.description]{task.description}"), | |
BarColumn(), | |
DownloadColumn(binary_units=True), | |
TextColumn("at"), | |
TransferSpeedColumn(), | |
TextColumn("in"), | |
TimeElapsedColumn(), | |
transient=True, | |
) as progress: | |
console.print(filename, style="bold") | |
send_task = progress.add_task("Sending", total=filesize) | |
bytes_sent = 0 | |
with open(filePath, "rb") as f: | |
while True: | |
data = f.read(self.bufferSize) | |
if not data: | |
break | |
bytes_sent += self.conn.send(data) | |
progress.update(send_task, completed=bytes_sent) | |
bytes_received = int(self.recv_msg()) | |
result = bytes_received == bytes_sent | |
self.send_msg(str(result)) | |
if result: | |
console.print(f"{filename} successfully sent", style="bold green") | |
except Exception as e: | |
return f"ERROR while sending: {e}" | |
def send_msg(self, msg: str) -> None: | |
""" | |
Sends a message to the initialized connection. | |
Args: | |
msg: The message to send. | |
""" | |
message_length = len(msg.encode()) | |
self.conn.send(message_length.to_bytes(4, byteorder="big")) | |
self.conn.send(msg.encode()) | |
def recv_msg(self) -> str: | |
""" | |
Receives a message from the initialized connection. | |
Returns: | |
str: The received message. | |
""" | |
msg_length_bytes = self.conn.recv(4) | |
msg_length = int.from_bytes(msg_length_bytes, byteorder="big") | |
del msg_length_bytes | |
msg = "" | |
while len(msg) < msg_length: | |
data = self.conn.recv(msg_length - len(msg)).decode() | |
if not data: | |
break # end of stream | |
msg += data | |
return msg | |
def archive(self, name: str, items: list) -> None: | |
""" | |
A method that creates Zipfile to archive files and directories. | |
Args: | |
name (str): to name the archived zip. may also contains path alongwith the filename to store it on different path. if not provided the archived file is created in the current working directory. | |
items (list): the paths of the files, directories to archive. | |
""" | |
with zipfile.ZipFile(f"{name}", "w", zipfile.ZIP_DEFLATED) as zf: | |
for item in items: | |
if os.path.isdir(item): | |
self.zipDir(item, zf) | |
elif os.path.isfile(item): | |
self.zipFile(item, zf) | |
def zipFile(self, path: str, ziph) -> None: | |
""" | |
write the file to the Zipfile. | |
Args: | |
path (str): path of file to be archive | |
ziph: Zipfile to write the file | |
""" | |
ziph.write( | |
path, os.path.basename(path.rstrip("\\" if os.name == "nt" else "/")) | |
) | |
def zipDir(self, path: str, ziph) -> None: | |
""" | |
write the directory to the Zipfile. | |
Args: | |
path (str): path of directory to be archive | |
ziph: Zipfile to write the directory | |
""" | |
for root, dirs, files in os.walk(path): | |
for file in files: | |
ziph.write( | |
os.path.join(root, file), | |
os.path.relpath(os.path.join(root, file), os.path.join(path, "..")), | |
) | |
def extract(src: str, dest: str = os.getcwd()) -> None: | |
"""extract the Zipfile file to the dest directory. | |
Args: | |
src (str): path of the archive to extract | |
dest: path to extract the archive. Defaults to current working directory | |
""" | |
with zipfile.ZipFile(src) as ziph: | |
ziph.extractall(dest) | |
# ------------------------------------------------------------------------------# | |
class Client: | |
""" | |
A class that implements the client side of file transfer. | |
Args: | |
serverHost (str): The IP address or hostname of the server. | |
serverPort (int): The port number of the server. | |
""" | |
def __init__(self, serverHost: str, serverPort: int): | |
self.serverHost = serverHost | |
self.serverPort = serverPort | |
def start_client(self) -> None: | |
""" | |
Starts the client. | |
""" | |
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as clientSocket: | |
clientSocket.connect((self.serverHost, self.serverPort)) | |
print(f"Connected to [{self.serverHost}:{self.serverPort}]") | |
self.response_server(clientSocket) | |
print("Connection closed") | |
def response_server(self, conn) -> None: | |
""" | |
Handles the response from the server and respond to the server to perform necessary actions. | |
Args: | |
conn: Socket for the connection to the server. | |
""" | |
# Initiates FileTransfer class to make communication between client and server | |
fileTransfer = FileTransfer(conn, 4096) | |
# Recieve Welcome message from the server | |
server_msg = fileTransfer.recv_msg() | |
# Print the received message | |
print(f"[SERVER] {server_msg}") | |
# ------------------------------------------------------------------------------# | |
class Server: | |
""" | |
A class for handling the server side of the file transfer. | |
Args: | |
host (str, optional): The IP address or hostname of the server. Defaults to loacl ip address. | |
port (int, optional): The port number of the server. Defaults to 9999. | |
maxClients (int, optional): The maximum number of clients that can be connected to the server. Defaults to 8. | |
""" | |
def __init__(self, host=None, port: int = 9999, maxClients: int = 8): | |
if not host: | |
self.host = self.get_ip() | |
else: | |
self.host = host | |
self.port = port | |
self.maxClients = maxClients | |
def start_server(self) -> None: | |
""" | |
Starts the server and accepts connections from clients. | |
""" | |
# Create a server socket | |
self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
# Bind the server socket to the given host and port | |
self.serverSocket.bind((self.host, self.port)) | |
# Start listening for client connections | |
self.serverSocket.listen(self.maxClients) | |
print(f"Server started on {self.host}:{self.port}") | |
# Accept connections from clients | |
self.accept_connections() | |
def accept_connections(self) -> None: | |
""" | |
Accepts connections from the clients. | |
""" | |
while True: | |
# Accept a client connection | |
conn, addr = self.serverSocket.accept() | |
# Handle the client connection in a separate thread | |
threading.Thread(target=self.client_handler, args=(conn, addr)).start() | |
def client_handler(self, conn, addr: tuple) -> None: | |
""" | |
Handles connected clients to the server. | |
Args: | |
conn: Socket representing the connection to the client. | |
addr (tuple): A tuple containing the IP address and port number of the client. | |
""" | |
print(f"New client connected: {addr[0]}:{addr[1]}") | |
# while True: | |
fileTransfer = FileTransfer(conn) | |
# Sends message to the client | |
fileTransfer.send_msg("Welcome to Our Server") | |
# Close the connection to the client | |
print(f"Closing connection to client {addr[0]}:{addr[1]}") | |
conn.close() | |
def accept_connections(self) -> None: | |
""" | |
Accepts incoming connections. | |
""" | |
while True: | |
# Accept a client connection | |
conn, addr = self.serverSocket.accept() | |
# Handle the client connection in a separate thread | |
start_new_thread(self.client_handler, (conn, addr)) | |
print(f"New client connected: {addr[0]}:{addr[1]}") | |
def get_ip(self) -> str: | |
""" | |
returns the local ip address | |
""" | |
hostname = socket.getfqdn() | |
return socket.gethostbyname_ex(hostname)[2][-1] |
This file contains hidden or 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
from pyft import Server, FileTransfer | |
import os, zipfile, shutil | |
class Reciever(Server): | |
def __init__(self, host=None, port: int = 9999, maxClients: int = 8): | |
super().__init__(host, port, maxClients) | |
self.start_server() | |
def accept_connections(self) -> None: | |
conn, addr = self.serverSocket.accept() | |
print(f"[{addr[0]}:{addr[1]}] connected.") | |
self.client_handler(conn, addr) | |
conn.close() | |
def client_handler(self, conn, addr) -> None: | |
ft = FileTransfer(conn, 102400) | |
path = '' # path to store recieve data | |
# temporary directory to store the received archived files/directories | |
# temp_dir = "recv_data" | |
# if not os.path.isdir(temp_dir): | |
# os.mkdir(temp_dir) | |
# number of files to recieve | |
files_count = int(ft.recv_msg()) | |
# hold the filenames for extraction | |
# filenames = [] | |
# receiving the files | |
for i in range(files_count): | |
filename = ft.recv_file(path) | |
# filenames.append(filename) # store the filenames for extraction | |
# extract the files/directories to the specified directory | |
# for name in filenames: | |
# self.extract(os.path.join(temp_dir, name), path) | |
# delete the temporary directory after extraction | |
# shutil.rmtree(temp_dir, ignore_errors=True) | |
if __name__ == "__main__": | |
recieve = Reciever() |
This file contains hidden or 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
import shutil | |
from pyft import Client, FileTransfer | |
import os, zipfile | |
class Sender(Client): | |
def __init__(self, serverHost: str, serverPort: int): | |
super().__init__(serverHost, serverPort) | |
self.start_client() | |
def response_server(self, conn) -> None: | |
ft = FileTransfer(conn, 102400) | |
items = [] # list of files paths to send | |
# # temporary directory to store the archived files | |
# temp_dir = "temp_data" | |
# if not os.path.isdir(temp_dir): | |
# os.mkdir(temp_dir) | |
# # to archive the file/directories paths in the list | |
# for item in items: | |
# ft.archive( | |
# os.path.join( | |
# temp_dir, | |
# os.path.basename(item.rstrip("\\" if os.name == "nt" else "/")), | |
# ), | |
# [item], | |
# ) | |
# send the files in the list | |
ft.send_msg(str(len(items))) | |
for item in items: | |
ft.send_file(item) | |
# # send the archived files | |
# for item in items: | |
# ft.send_file( | |
# os.path.join( | |
# temp_dir, | |
# os.path.basename(item.rstrip("\\" if os.name == "nt" else "/")), | |
# ) | |
# ) | |
# # delete the temporary directory | |
# shutil.rmtree(temp_dir, ignore_errors=True) | |
if __name__ == "__main__": | |
host, port = input("provide the address shown on the reciever's script-> ").split(':') | |
send = Sender(host, int(port)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment