""" Lab009. Расчёт параметров передачи реальной фотографии. Программа: 1. Читает реальный JPEG-файл. 2. Определяет его разрешение и размер. 3. Разбивает файл на фрагменты. 4. Формирует реальные пакеты протокола. 5. Учитывает ACK для каждого фрагмента. 6. Рассчитывает время передачи при разных скоростях канала. Расчёт времени показывает чистое эфирное время без учёта: - пауз между пакетами; - обработки на передатчике и приёмнике; - ожидания ACK; - повторных передач; - преамбулы и служебных символов физического уровня. """ from pathlib import Path from PIL import Image from protocol.image_fragments import ( FRAGMENT_HEADER_SIZE, encode_image_fragment, split_image_bytes, ) from protocol.packet import ( CRC_SIZE, HEADER_SIZE, MESSAGE_TYPE_ACK, MESSAGE_TYPE_IMAGE_FRAGMENT, build_packet, ) # ============================================================ # Настройки # ============================================================ SOURCE_PATH = Path( "data/raw/lab009_source.jpg" ) IMAGE_ID = 20260713 # Сколько байтов JPEG помещается в один фрагмент. FRAGMENT_DATA_SIZE = 512 # Скорости полезного цифрового канала в кбит/с. BITRATES_KBPS = [ 1, 5, 10, 20, 50, 100, 200, ] # ============================================================ # Вспомогательные функции # ============================================================ def format_bytes(byte_count: int) -> str: """ Представить размер одновременно в байтах и килобайтах. """ kibibytes = byte_count / 1024 return ( f"{byte_count} байт " f"({kibibytes:.2f} КиБ)" ) def format_duration(seconds: float) -> str: """ Представить длительность в удобной форме. """ if seconds < 1: return f"{seconds * 1000:.1f} мс" if seconds < 60: return f"{seconds:.2f} с" minutes = int(seconds // 60) remaining_seconds = seconds % 60 return ( f"{minutes} мин " f"{remaining_seconds:.1f} с" ) # ============================================================ # Проверка исходного файла # ============================================================ if not SOURCE_PATH.exists(): raise FileNotFoundError( f"Не найден файл: {SOURCE_PATH}" ) source_bytes = SOURCE_PATH.read_bytes() if not source_bytes: raise ValueError( "Исходный JPEG-файл пуст" ) # ============================================================ # Чтение параметров изображения # ============================================================ with Image.open(SOURCE_PATH) as image: image_format = image.format image_mode = image.mode image_width, image_height = image.size # Проверяем, что файл действительно читается как изображение. image.verify() # ============================================================ # Разбиение JPEG на фрагменты # ============================================================ fragments = split_image_bytes( image_bytes=source_bytes, image_id=IMAGE_ID, fragment_data_size=FRAGMENT_DATA_SIZE, ) # ============================================================ # Формирование DATA-пакетов # ============================================================ data_packets = [] for fragment in fragments: fragment_payload = encode_image_fragment( fragment ) packet = build_packet( payload=fragment_payload, message_type=MESSAGE_TYPE_IMAGE_FRAGMENT, sequence_number=fragment.fragment_index, ) data_packets.append(packet) # ============================================================ # Формирование типового ACK-пакета # ============================================================ ack_packet = build_packet( payload=b"", message_type=MESSAGE_TYPE_ACK, sequence_number=0, ) ack_packet_size = len(ack_packet) # ============================================================ # Расчёт объёмов # ============================================================ source_size = len(source_bytes) fragment_count = len(fragments) # Полный размер всех DATA-пакетов. total_data_packet_bytes = sum( len(packet) for packet in data_packets ) # Для каждого DATA-пакета ожидается один ACK. total_ack_bytes = ( fragment_count * ack_packet_size ) # Идеальный радиообмен: # каждый DATA и каждый ACK переданы ровно один раз. total_radio_bytes = ( total_data_packet_bytes + total_ack_bytes ) total_radio_bits = ( total_radio_bytes * 8 ) # ============================================================ # Детализация служебных расходов # ============================================================ total_fragment_header_bytes = ( fragment_count * FRAGMENT_HEADER_SIZE ) data_protocol_overhead = ( fragment_count * (HEADER_SIZE + CRC_SIZE) ) total_overhead_bytes = ( total_radio_bytes - source_size ) payload_efficiency = ( source_size / total_radio_bytes * 100 ) # ============================================================ # Вывод параметров фотографии # ============================================================ print("=== Lab009. Расчёт передачи реальной фотографии ===") print("\nИсходный файл:") print(SOURCE_PATH) print("\nФормат изображения:") print(image_format) print("\nЦветовой режим:") print(image_mode) print("\nРазрешение:") print( image_width, "x", image_height, "пикселей", ) print("\nРазмер исходного JPEG:") print( format_bytes(source_size) ) # ============================================================ # Вывод структуры передачи # ============================================================ print("\n--- Фрагментация ---") print( "Размер данных одного полного фрагмента:", FRAGMENT_DATA_SIZE, "байт", ) print( "Количество фрагментов:", fragment_count, ) print( "Размер заголовка фрагмента:", FRAGMENT_HEADER_SIZE, "байт", ) print( "Размер заголовка основного пакета:", HEADER_SIZE, "байт", ) print( "Размер CRC-32:", CRC_SIZE, "байт", ) print( "Размер ACK-пакета:", ack_packet_size, "байт", ) # ============================================================ # Вывод полного объёма # ============================================================ print("\n--- Объём передачи без повторов ---") print("Исходные данные JPEG:") print( format_bytes(source_size) ) print("\nЗаголовки фрагментов:") print( format_bytes(total_fragment_header_bytes) ) print("\nСлужебные поля и CRC DATA-пакетов:") print( format_bytes(data_protocol_overhead) ) print("\nВсе DATA-пакеты:") print( format_bytes(total_data_packet_bytes) ) print("\nВсе ACK-пакеты:") print( format_bytes(total_ack_bytes) ) print("\nПолный радиообмен DATA + ACK:") print( format_bytes(total_radio_bytes) ) print("\nОбщие служебные расходы:") print( format_bytes(total_overhead_bytes) ) print( "\nПолезная эффективность протокола:" ) print( f"{payload_efficiency:.2f} %" ) # ============================================================ # Расчёт времени передачи # ============================================================ print("\n--- Чистое эфирное время ---") print( "Скорость DATA без ACK DATA + ACK" ) for bitrate_kbps in BITRATES_KBPS: bitrate_bits_per_second = ( bitrate_kbps * 1000 ) data_time_seconds = ( total_data_packet_bytes * 8 / bitrate_bits_per_second ) full_time_seconds = ( total_radio_bits / bitrate_bits_per_second ) print( f"{bitrate_kbps:>4} кбит/с" f" " f"{format_duration(data_time_seconds):>12}" f" " f"{format_duration(full_time_seconds):>12}" ) # ============================================================ # Сценарий с повторами # ============================================================ # Коэффициент 2 означает, что суммарно передано # примерно в два раза больше байтов из-за ошибок, # потерь и повторных передач. RETRY_TRAFFIC_MULTIPLIER = 2.0 print( "\n--- Оценка при удвоении трафика из-за повторов ---" ) for bitrate_kbps in BITRATES_KBPS: bitrate_bits_per_second = ( bitrate_kbps * 1000 ) estimated_seconds = ( total_radio_bits * RETRY_TRAFFIC_MULTIPLIER / bitrate_bits_per_second ) print( f"{bitrate_kbps:>4} кбит/с" f" " f"{format_duration(estimated_seconds)}" ) # ============================================================ # Автоматические проверки # ============================================================ assert fragment_count > 0 assert ( total_data_packet_bytes > source_size ) assert ( total_radio_bytes > total_data_packet_bytes ) assert ( 0 < payload_efficiency < 100 ) print( "\nПроверка пройдена: " "параметры передачи фотографии рассчитаны." )