#!/usr/bin/env python3
# so_sanh_toc_do.py - đo tốc độ: quét TUẦN TỰ so với quét SONG SONG (threading).
# Phần A: quét THẬT cổng của CHÍNH MÁY BẠN (127.0.0.1) - an toàn, không gửi gì ra ngoài.
# Phần B: MÔ PHỎNG cổng "im lặng" (filtered) bằng time.sleep(), vì trên loopback
#         cổng đóng luôn trả lời ngay nên không thể thấy rõ lợi ích của threading.
import socket
import time
from concurrent.futures import ThreadPoolExecutor

HOST = "127.0.0.1"
PORTS_A = list(range(1, 4001))   # phần A: 4000 cổng đầu tiên trên máy bạn
TIMEOUT_A = 0.3                  # số giây chờ mỗi cổng ở phần A
SLOW_TASKS = 30                  # phần B: số "cổng" giả lập
SLOW_WAIT = 0.3                  # số giây mỗi "cổng" giả lập phải chờ - giống TIMEOUT_A


def probe(port):
    # Mở một socket TCP, thử connect(). connect_ex() trả về 0 nếu thành công,
    # trả về MÃ LỖI (số nguyên, ví dụ 61 là "từ chối") nếu không - khác với
    # connect() thường, sẽ ném exception thay vì trả về số.
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.settimeout(TIMEOUT_A)
        return port, s.connect_ex((HOST, port)) == 0


def slow_probe(n):
    # MÔ PHỎNG một cổng "im lặng" (filtered): không ai trả lời, nên phải chờ
    # HẾT timeout mới biết. time.sleep() dùng thay cho connect() thật, chỉ
    # để demo an toàn và cho ra số giống nhau mỗi lần chạy. Code THẬT làm việc
    # này là probe() trong port_scan.py, khi gặp loại "filtered".
    # [1] Đứng chờ SLOW_WAIT giây, như một cổng im lặng làm ta phải chờ hết timeout
    time.sleep(SLOW_WAIT)
    # [2] Trả về số thứ tự của "cổng" này để biết tác vụ nào đã xong
    return n


def run_sequential(fn, items):
    return [fn(x) for x in items]


def run_threaded(fn, items, workers):
    # ThreadPoolExecutor: trong lúc một luồng đang CHỜ (I/O - chờ socket, chờ
    # sleep), Python chuyển sang chạy luồng khác ngay, nên tổng thời gian gần
    # bằng thời gian của phần CHẬM NHẤT, không phải TỔNG thời gian tất cả.
    with ThreadPoolExecutor(max_workers=workers) as pool:
        return list(pool.map(fn, items))


def phan_a():
    print(f"--- PHAN A: quet THAT {len(PORTS_A)} cong tren {HOST} ---")
    start = time.time()
    seq = run_sequential(probe, PORTS_A)
    seq_time = time.time() - start

    start = time.time()
    par = run_threaded(probe, PORTS_A, workers=200)
    par_time = time.time() - start

    print(f"TUAN TU   : {seq_time:6.2f} giay")
    print(f"SONG SONG : {par_time:6.2f} giay")
    print("Nhan xet: tren loopback, cong dong tra loi NGAY (RST), hau nhu khong co gi de 'cho'.")
    print("Vi vay threading it hoac khong giup gi o day - co khi con cham hon vi ton chi phi tao luong.\n")


def phan_b():
    print(f"--- PHAN B: mo phong {SLOW_TASKS} cong 'im lang', moi cong cho {SLOW_WAIT}s ---")
    start = time.time()
    run_sequential(slow_probe, range(SLOW_TASKS))
    seq_time = time.time() - start

    start = time.time()
    run_threaded(slow_probe, range(SLOW_TASKS), workers=SLOW_TASKS)
    par_time = time.time() - start

    print(f"TUAN TU   : {seq_time:6.2f} giay   (~ {SLOW_TASKS} x {SLOW_WAIT}s)")
    print(f"SONG SONG : {par_time:6.2f} giay   (~ 1 x {SLOW_WAIT}s, ca {SLOW_TASKS} cong cung cho)")
    if par_time > 0:
        print(f"Nhanh hon khoang {seq_time / par_time:.0f} lan - day moi la luc threading co ich thuc su.")


if __name__ == "__main__":
    phan_a()
    phan_b()
