#!/usr/bin/env python3
"""
send_file.py - Split a file into 3KB chunks and send them to a remote host.
Usage:
python send_file.py
"""
import argparse
import socket
import struct
import sys
import time
CHUNK_SIZE = 3 * 1024 # 3 KB
MAX_RETRIES = 10
TIMEOUT_SECONDS = 5
RETRY_DELAY_SECONDS = 2
def send_chunk(host: str, port: int, chunk_index: int, total_chunks: int, data: bytes) -> None:
"""
Send a single chunk to the remote host with retry logic.
"""
payload = data
size = len(payload)
for attempt in range(1, MAX_RETRIES + 1):
try:
with socket.create_connection((host, port), timeout=TIMEOUT_SECONDS) as sock:
sock.sendall(payload)
print(f" Chunk {chunk_index + 1}/{total_chunks} of {size} bytes sent successfully (attempt {attempt}).")
time.sleep(1)
return
except (socket.timeout, TimeoutError):
print(f" Chunk {chunk_index + 1}/{total_chunks}: timeout on attempt {attempt}/{MAX_RETRIES}.")
except OSError as exc:
print(f" Chunk {chunk_index + 1}/{total_chunks}: error on attempt {attempt}/{MAX_RETRIES}: {exc}")
if attempt < MAX_RETRIES:
print(f" Retrying in {RETRY_DELAY_SECONDS}s...")
time.sleep(RETRY_DELAY_SECONDS)
raise RuntimeError(
f"Failed to send chunk {chunk_index + 1}/{total_chunks} after {MAX_RETRIES} attempts."
)
def split_and_send(filename: str, host: str, port: int) -> None:
"""Read the file, split it into chunks, and send each chunk."""
try:
with open(filename, "rb") as f:
file_data = f.read()
except FileNotFoundError:
sys.exit(f"Error: file '{filename}' not found.")
except OSError as exc:
sys.exit(f"Error reading '{filename}': {exc}")
chunks = [file_data[i : i + CHUNK_SIZE] for i in range(0, len(file_data), CHUNK_SIZE)]
total_chunks = len(chunks)
if total_chunks == 0:
sys.exit("Error: file is empty.")
print(f"File: '{filename}' ({len(file_data)} bytes) -> {total_chunks} chunk(s) of up to {CHUNK_SIZE} bytes.")
print(f"Destination: {host}:{port} (timeout={TIMEOUT_SECONDS}s, max_retries={MAX_RETRIES})\n")
for index, chunk in enumerate(chunks):
send_chunk(host, port, index, total_chunks, chunk)
print("\nAll chunks sent successfully.")
def main() -> None:
parser = argparse.ArgumentParser(description="Send a file in 3 KB chunks over TCP.")
parser.add_argument("filename", help="Path to the file to send.")
parser.add_argument("host", help="Remote host IP or hostname.")
parser.add_argument("port", type=int, help="Remote port number.")
args = parser.parse_args()
split_and_send(args.filename, args.host, args.port)
if __name__ == "__main__":
main()