hilo
hilo

Reputation: 11

Shows loading progress and threads running at the same time in Transfer File socket python

i have 2 problems that need help below. Transfer File socket python and MultiThreading

# server.py
import os
import socket
import threading

RESOURCES_SERVER = 'resources'
TEXT_FILE = 'text.txt'
BUFFER_SIZE = 4096 

init_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
init_server.bind((socket.gethostname(), 9876))
init_server.listen(5)
file_lock = threading.Lock()
print(f"Server started at {init_server.getsockname()}")

def handle_client(server, addr):
    try:
        text_file = os.path.join(os.path.dirname(__file__), TEXT_FILE)
        resources_dir = os.path.join(os.path.dirname(__file__), RESOURCES_SERVER)
        
        with open(text_file, 'r') as f:
            file_list = f.read()
        server.send(file_list.encode())

        while True:
            request = server.recv(BUFFER_SIZE).decode().strip()
            if not request:
                break
            
            if '|' not in request:
                file_path = os.path.join(resources_dir, request)
                if os.path.isfile(file_path):
                    file_size = os.path.getsize(file_path)
                    server.send(str(file_size).encode())
                else:
                    server.send(f"File {request} not found.".encode())
                continue

            parts = request.split('|')
            if len(parts) != 2:
                server.send("Invalid request.".encode())
                continue

            file_name, range_str = parts
            start, end = map(int, range_str.split('-'))
            file_path = os.path.join(resources_dir, file_name)

            if not os.path.isfile(file_path):
                server.send(f"File {file_name} not found.".encode())
                continue

            with file_lock:
                with open(file_path, 'rb') as f:
                    f.seek(start)
                    remaining = end - start
                    
                    while remaining > 0:
                        chunk_size = min(BUFFER_SIZE, remaining)
                        chunk = f.read(chunk_size)
                        if not chunk:
                            break
                        server.sendall(chunk)
                        remaining -= len(chunk)

    except ConnectionResetError:
        print(f"Connection reset by peer {addr}")
    except Exception as e:
        print(f"Error handling client {addr}: {e}")
    finally:
        server.close()

def run_server():
    write_text()
    try:
        while True:
            server, addr = init_server.accept()
            print(f"Connected by {addr}")
            thread = threading.Thread(target=handle_client, args=(server, addr))
            thread.daemon = True
            thread.start()
    except KeyboardInterrupt:
        print("\nServer is shutting down...")
    finally:
        server.close()

if __name__ == "__main__":
    run_server()
# client.py
import os
import socket
import threading
import sys
import time

REQUEST_DOWNLOAD_FILE = "input.txt"
DOWNLOAD_FOLDER = "data"
BUFFER_SIZE = 4096
PORT = 9876

lock = threading.Lock()
last_used_line_in_terminal = 0

...

def display_progress_download(base_line, part_num, percent):
    with lock:
        target_line = base_line + part_num
        sys.stdout.write(f'\033[{target_line}H')
        sys.stdout.write('\033[2K')
        sys.stdout.write(f"Part {part_num} - Progress: {percent:.2f}% / 100%\r")
        sys.stdout.flush()
        time.sleep(0.01)

def download_part(HOST, PORT, file_name, part_num, start, end, base_line, part_size):
    client = None
    try:
        client = create_connection_to_server(HOST, PORT)
        if not client:
            return False

        client.recv(BUFFER_SIZE)

        request = f"{file_name}|{start}-{end}".encode()
        client.sendall(request)

        part_path = f"./{DOWNLOAD_FOLDER}/{file_name}.part{part_num}"
        with open(part_path, "wb") as f:
            received = 0
            while received < part_size:
                chunk_size = min(BUFFER_SIZE, part_size - received)
                data = client.recv(chunk_size)
                if not data:
                    raise ConnectionError(f"Connection lost at {received} bytes")
                f.write(data)
                received += len(data)
                percent = min(received / part_size * 100, 100)
                display_progress_download(base_line, part_num, percent)
        return True

    except Exception as e:
        print(f"Error downloading part {part_num}: {e}")
        return False
    finally:
        if client:
            client.close()

def merge_parts(file_name, parts):
    output_path = f"./{DOWNLOAD_FOLDER}/{file_name}"
    try:
        with open(output_path, "wb") as output_file:
            for part_path in parts:
                with open(part_path, "rb") as part_file:
                    while chunk := part_file.read(BUFFER_SIZE):
                        output_file.write(chunk)
                os.remove(part_path)
        return True
    except Exception as e:
        return False

def download_file(HOST, PORT, file_name, file_size):
    global last_used_line_in_terminal
    os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
    
    threads = []
    parts = []

    base_line = last_used_line_in_terminal + 1
    last_used_line_in_terminal = base_line + 6

    print(f"\033[{base_line}HDownloading {file_name}...\n")

    for i in range(4):
        start = i * (file_size // 4)
        end = file_size if i == 3 else (i + 1) * (file_size // 4)
        
        part_size = end - start
        
        thread = threading.Thread(target=download_part, args=(HOST, PORT, file_name, i + 1, start, end, base_line, part_size))
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()

    for i in range(4):
        part_path = f"./{DOWNLOAD_FOLDER}/{file_name}.part{i + 1}"
        if not os.path.exists(part_path):
            return False
        parts.append(part_path)

    success = merge_parts(file_name, parts)
    try:
        merged_file_size = os.path.getsize(f"./{DOWNLOAD_FOLDER}/{file_name}")
        if success and merged_file_size == file_size:
            print(f"\033[{base_line + 5}HDownload {file_name} completed successfully!\n")
            print(f"\033[{base_line + 6}H" + "-" * 40)
        else:
            print(f"\033[{base_line + 5}HDownload {file_name} errors! Expected:{file_size}, Got:{merged_file_size}\n")
    except FileNotFoundError:
        print(f"\033[{base_line + 5}HDownload of {file_name} failed! Merged file not found.")
    return success


Here is my client and server code. It will download files from the server to the client. The client will create 4 parallel connections to the server, each connection will be responsible for downloading a portion of the original file size (with the original file size divided into 4) and I need to display their progress on the screen. .

Now the questions I want to ask.

  1. I used ANSI Escape Code to display the progress by moving the cursor and deleting the old number and writing the new number, it works quite well but if the terminal doesn't have enough length, it will be overwritten at the last line same at the terminal. I want to see if there is a way to display 4 other streams or fix this error.
example display process
Host Name: 127.0.0.1
Available files:
genshin.png|5980541
arya.gif|3707944
jinx.png|2445272
alime.jpeg|23765
avt.jpeg|51573

Downloading arya.gif...
Part 1 - Progress: 100.00% / 100%
Part 2 - Progress: 100.00% / 100%
Part 3 - Progress: 100.00% / 100%
Part 4 - Progress: 100.00% / 100%
Download completed successfully for arya.gif!
----------------------------------------
Downloading jinx.png...
Part 1 - Progress: 100.00% / 100%
Part 2 - Progress: 100.00% / 100%
Part 3 - Progress: 100.00% / 100%
Part 4 - Progress: 100.00% / 100%
Download completed successfully for jinx.png!
  1. I used python's logging library to check the bytes transmitted between the server and client and found out that they do not seem to be at the same time. Specifically as follows:
2024-12-15 00:33:37,379 - INFO - Received: 2260992 bytes in part 1 of vangoh.jpg
2024-12-15 00:33:37,387 - INFO - Received: 2342912 bytes in part 1 of vangoh.jpg
2024-12-15 00:33:37,395 - INFO - Received: 2424832 bytes in part 1 of vangoh.jpg
2024-12-15 00:33:37,396 - INFO - Received: 81816 bytes in part 2 of vangoh.jpg
2024-12-15 00:33:37,404 - INFO - Received: 2506752 bytes in part 1 of vangoh.jpg
2024-12-15 00:33:37,412 - INFO - Received: 163736 bytes in part 2 of vangoh.jpg
2024-12-15 00:33:37,420 - INFO - Received: 2588672 bytes in part 1 of vangoh.jpg

This is part of the process of transmitting bytes, and when part 1 has transmitted about 80%, part 2 will start transmitting, while I want all 4 parts to transmit in parallel at the same time (there may be a difference but it is small), this causes my progress to sometimes display part 1 at about 80/100% before part 2 begins to load. I want to see how to fix this error

Maybe the article is a bit long but I hope everyone can support me, remember to keep my functions intact. Thank you very much everyone.

i tried printing the progress but if ter minal is not enough length it will be overwritten at the end. I also tried downloading the file by dividing it into 4 parallel parts, but the 4 threads do not run at the same time, but sometimes part2 appears before part1, I want all 4 to run at the same time.

Upvotes: 1

Views: 39

Answers (0)

Related Questions