Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 2

.pdf
Скачиваний:
24
Добавлен:
23.12.2021
Размер:
1.17 Mб
Скачать

{

// Сброс

free(packet), packet = NULL; // Получение размера пакета

packet_size = atol(query + q_packet_size_n); // Если указан допустимый размер

if (1 <= packet_size && packet_size <= file_size)

{

//Выделяем память под пакет (на 1 больше для индикации ошибки: + или -) packet = (char *)malloc((size_t)(packet_size + 1) * sizeof(char));

//Вычисляем число пакетов (с округлением в большую сторону)

n_packets = (file_size + packet_size - 1) / packet_size; // Одобрено

socket_write_with_sim(socket_fd, "+", 1, p_sa, sa_len, LOSS_PROB, MODIF_PROB, /*ID*/ 3);

}

else // Не одобрено

socket_write_with_sim(socket_fd, "-", 1, p_sa, sa_len, LOSS_PROB, MODIF_PROB, /*ID*/ 3);

}

// Если запрашивается пакет, запрошенный файл открыт и память для пакета выделена else if (requested_file && packet && strncmp(query, q_packet_s, q_packet_n) == 0)

{

// Получение индекса пакета

const long packet_i = atol(query + q_packet_n); #if IS_DEBUG == 1

if (packet_i == 0) printf("\n");

#endif

if (packet_i < 0 || packet_i >= n_packets) // Если индекс выходит за диапазон

{

// Индикация: - (есть ошибка)

socket_write_with_sim(socket_fd, "-", 1, p_sa, sa_len, LOSS_PROB, MODIF_PROB, /*ID*/ 4);

continue;

}

// Получаем индекс пакета из текущей позиции указателя const long packet_cur = ftell(requested_file) / packet_size;

//Переход по пакетам происходит относительно текущей позиции fseek(requested_file, packet_size * (packet_i - packet_cur), SEEK_CUR);

//Чтение пакета из файла во временную строку

symbols = 1 + fread(packet + 1, sizeof(char), (size_t)packet_size, requested_file);

//Индикация: + (нет ошибки) packet[0] = '+';

//Отправление временной строки в сокет

#if IS_DEBUG != 1

socket_write_with_sim(socket_fd, packet, symbols, p_sa, sa_len, LOSS_PROB, MODIF_PROB, /*ID*/ 4 + packet_i);

#else

int status = socket_write_with_sim(socket_fd, packet, symbols, p_sa, sa_len, LOSS_PROB, MODIF_PROB, /*ID*/ 4 + packet_i);

if (status == 0 && packet_i + 1 == n_packets) printf("\n");

#endif

}

else

{

if (query[0] == 'X')

{

if (requested_file)

fclose(requested_file), requested_file = NULL; free(packet), packet = NULL;

}

else // Запрос не распознан

socket_write_with_sim(socket_fd, "-", 1, p_sa, sa_len, LOSS_PROB, MODIF_PROB, /*ID*/ 1);

}

}

close(socket_fd); return EXIT_SUCCESS;

}

21

Реализация UDPgd-клиента на языке C приведена в таблице 6.

Таблица 6. Реализация UDPgd-клиента на языке C

udpgd_client.c

// udpgd_client.c

#include <arpa/inet.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/types.h> #include <time.h> #include <unistd.h>

#define PORT 1100 #define HOST "0.0.0.0" #define QUERY_SIZE 200

#define FILENAME_LENGTH 50 // не включая '\0' #define FILESIZE_LENGTH 20 // не включая '\0' #define DOWNLOAD_PREFIX "download_"

#define IS_DEBUG 0 // debug-режим (выводит ошибки отправки пакетов)

#define WAITING_TIME 50000 // макс. ожидание данных от сервера в микросекундах #define ATTEMPTS 1000 // число попыток получения данных от сервера

//Имитация проблем на соединении

//999 успешных доставок на 1000 отправлений

#define SIMULATE_CONNECTION_PROBLEMS 1

//Вероятность потери пакета (аналог коэффициента потери IP-пакетов)

#define LOSS_PROB 0.0009f

//Вероятность случайной модификации пакета (аналог коэффициента ошибок пакетов IP)

#define MODIF_PROB 0.00009f

#define SUCCESS 0 #define ERROR_CODE -1 #define FILE_NOT_FOUND -2

#define INVALID_PACKET_SIZE -3 #define FILE_OPEN_ERROR -4 #define CONNECTION_UNSTABLE -5

// Функция возвращает процессорное время процесса в секундах

double get_proc_time(void) { return (double)clock() / CLOCKS_PER_SEC; }

//Функция вычисляет контрольную сумму CRC-16 CCITT

//Контракт: buffer != nullptr, n >= 0

uint16_t calculate_crc16(const char *buffer, size_t n)

{

uint16_t crc = 0xFFFF; while (n--)

{

crc ^= (uint16_t)(*buffer++ << 8); for (uint8_t i = 0; i < 8; i++)

crc = (uint16_t)(crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1);

}

return crc;

}

//Функция отправляет строку в сокет

//Контракт: socket_fd != -1, buffer != nullptr, n >= 1, sa != nullptr, sa_len == sizeof(*sa)

size_t socket_write(const int socket_fd, const char *buffer_in, const size_t n, struct sockaddr *sa, socklen_t sa_len, uint16_t checksum)

{

// Размер буфера: 2 байта (16 бит) на контрольную сумму

+ сам пакет

size_t buffer_out_size = sizeof(uint16_t) + sizeof(char) * n;

char *buffer_out =

malloc(buffer_out_size);

 

checksum = (checksum == 0) ? calculate_crc16(buffer_in,

n) : checksum;

memcpy(buffer_out,

&checksum, sizeof(uint16_t));

// = [checksum][-undefined-]

memcpy(buffer_out + sizeof(uint16_t), buffer_in, sizeof(char) * n); // = [checksum][packet] ssize_t bytes = 0;

if ((bytes = sendto(socket_fd, buffer_out, buffer_out_size, 0, sa, sa_len)) <= ERROR_CODE)

{

perror("sendto operation failed");

22

close(socket_fd); free(buffer_out); exit(EXIT_FAILURE);

}

free(buffer_out);

// Число записанных символов

return ((size_t)bytes - sizeof(uint16_t)) / sizeof(char);

}

//Функция читает строку заданной длины из сокета

//Контракт: socket_fd != -1, buffer != nullptr, n >= 1, sa != nullptr, sa_len == sizeof(*sa) size_t socket_read(const int socket_fd, char *buffer, const size_t n, struct sockaddr *sa,

socklen_t sa_len, long microseconds, long id)

{

if (microseconds >

0)

 

{

 

 

// Ожидание ответа

 

fd_set readfds;

 

 

FD_ZERO(&readfds);

// Очистка набора дескрипторов

FD_SET(socket_fd, &readfds); // Добавление socket_fd в набор

struct timeval

tv;

 

tv.tv_sec = 0;

 

// секунды

tv.tv_usec = microseconds; // микросекунды

if (select(socket_fd + 1, &readfds, NULL, NULL, &tv) <= 0) return 0; // Ничего не пришло

}

// Размер буфера: 2 байта (16 бит) на контрольную сумму + сам пакет size_t buffer_out_size = sizeof(uint16_t) + sizeof(char) * n;

char *buffer_out = malloc(buffer_out_size);

//Считывание данных ssize_t bytes = 0;

if ((bytes = recvfrom(socket_fd, buffer_out, buffer_out_size, 0, sa, &sa_len)) <= ERROR_CODE)

{

perror("read operation failed"); close(socket_fd); free(buffer_out); exit(EXIT_FAILURE);

}

//Число прочитанных символов

size_t symbols = ((size_t)bytes - sizeof(uint16_t)) / sizeof(char); // Проверка контрольной суммы

uint16_t received_checksum = 0; memcpy(&received_checksum, buffer_out, sizeof(uint16_t));

memcpy(buffer, buffer_out + sizeof(uint16_t), sizeof(char) * symbols); free(buffer_out);

uint16_t checksum = calculate_crc16(buffer, symbols) + (uint16_t)id; return received_checksum == checksum ? symbols : 0;

}

//Функция имитации потери / модификации пакета

//Контракт: socket_fd != -1, buffer_in != nullptr, n >= 1, sa != nullptr, sa_len == sizeof(*sa)

//loss_prob -- вероятность потери пакета (от 0.0 до 1.0)

//modif_prob -- вероятность модификации пакета (от 0.0 до 1.0)

int socket_write_with_sim(const int socket_fd, const char *buffer_in, const size_t n,

struct sockaddr *sa, socklen_t sa_len, float loss_prob, float modif_prob)

{

//Создание массива для модификации

//(неконстантная копия аргумента buffer_in) size_t buffer_size = sizeof(char) * n;

char *buffer = malloc(buffer_size); memcpy(buffer, buffer_in, buffer_size);

#if SIMULATE_CONNECTION_PROBLEMS == 1 // Сумма вероятностей

float sum = loss_prob + (loss_prob + modif_prob);

//Если выход за диапазон [0.0, 1.0], то значения нормализуются if (sum < 0 || sum > 1)

{

loss_prob /= sum; modif_prob /= sum;

}

modif_prob += loss_prob;

//Выбор - что делать с пакетом:

//1. Не посылать (имитация потери пакета). loss_prob_p=0.0005

//=> 5 ошибок на 10000 отправлений

//2. Модифицировать. modif_prob=0.0005

//=> 5 ошибок на 10000 отправлений

23

//3. Ничего не делать (отправить без ошибок)

//=> 9990 успешных доставок на 10000 отправлений

//=> 999 успешных доставок на 1000 отправлений const int accuracy_inv = 10000;

float v = (float)(rand() % (accuracy_inv + 1)) / (float)accuracy_inv; if (v < loss_prob) // Имитация потери пакета

{

#if IS_DEBUG == 1

printf("-- Packet dropped\n");

printf("Packet: %.*s\n", (int)buffer_size, buffer);

#endif

fflush(stdout);

free(buffer); return 1;

}

uint16_t checksum = calculate_crc16(buffer, buffer_size); if (v < modif_prob) // Имитация модификации пакета

{

#if IS_DEBUG == 1

printf("-- Packet modification\n");

printf("Packet: %.*s\n", (int)buffer_size, buffer);

#endif

int modification_done = 0;

for (size_t i = 0; i < buffer_size; ++i)

if ((float)(rand() % (accuracy_inv + 1)) / (float)accuracy_inv < modif_prob) buffer[i] ^= (char)(rand() % 256), modification_done = 1;

if (!modification_done && buffer_size) // Хотя бы одна модификация будет сделана ++buffer[(size_t)rand() % buffer_size];

#if IS_DEBUG == 1

printf("Modified: %.*s\n", (int)buffer_size, buffer);

#endif

socket_write(socket_fd, buffer, buffer_size / sizeof(char), sa, sa_len, checksum); free(buffer);

fflush(stdout); return 2;

}

// Без имитации

socket_write(socket_fd, buffer, buffer_size / sizeof(char), sa, sa_len, checksum); free(buffer);

return 0;

#else // SIMULATE_CONNECTION_PROBLEMS == 0

(void)loss_prob; // чтобы избежать предупреждения "unused variable" (void)modif_prob; // чтобы избежать предупреждения "unused variable" socket_write(socket_fd, buffer, buffer_size / sizeof(char), sa, sa_len, 0); free(buffer);

return 0; #endif

}

//Функция отправляет запрос на получение статуса файла (существует или нет)

//Контракт: socket_fd != -1, filename != nullptr, 1 <= strlen(filename) < FILENAME_LENGTH,

//sa != nullptr, sa_len == sizeof(*sa)

int socket_get_file_status(const int socket_fd, const char *filename, struct sockaddr *sa, socklen_t sa_len)

{

char query[QUERY_SIZE], b_filename_exists = '-'; snprintf(query, QUERY_SIZE, "STATUS %s", filename); size_t symbols = 0;

for (int i = 0; i < ATTEMPTS && symbols == 0; ++i)

{

socket_write_with_sim(socket_fd, query, strlen(query), sa, sa_len, 0.0, 0.0);

symbols = socket_read(socket_fd, &b_filename_exists, 1, sa, sa_len, WAITING_TIME, /*ID*/ 1);

}

if (symbols == 0)

return CONNECTION_UNSTABLE;

return b_filename_exists == '+' ? SUCCESS : FILE_NOT_FOUND;

}

//Функция отправляет запрос на получение размера файла

//Контракт: socket_fd != -1, sa != nullptr, sa_len == sizeof(*sa)

long socket_get_file_size(const int socket_fd, struct sockaddr *sa, socklen_t sa_len)

{

char file_size[FILESIZE_LENGTH + 1]; size_t symbols = 0;

for (int i = 0; i < ATTEMPTS && symbols == 0; ++i)

{

24

socket_write_with_sim(socket_fd, "SIZE", 4, sa, sa_len, LOSS_PROB, MODIF_PROB); symbols =

socket_read(socket_fd, file_size, FILESIZE_LENGTH, sa, sa_len, WAITING_TIME, /*ID*/ 2);

}

if (symbols == 0)

return CONNECTION_UNSTABLE; file_size[symbols] = '\0';

return ('0' <= file_size[0] && file_size[0] <= '9') ? atol(file_size) : FILE_NOT_FOUND;

}

//Функция отправляет запрос на установку размера пакета

//Контракт: socket_fd != -1, 1 <= packet_size <= file_size, sa != nullptr, sa_len == sizeof(*sa) int socket_set_packet_size(const int socket_fd, const long packet_size, struct sockaddr *sa,

socklen_t sa_len)

{

char query[QUERY_SIZE], b_packet_size = '\0'; snprintf(query, QUERY_SIZE, "PACKET SIZE %ld", packet_size); size_t symbols = 0;

for (int i = 0; i < ATTEMPTS && symbols == 0; ++i)

{

socket_write_with_sim(socket_fd, query, strlen(query), sa, sa_len, LOSS_PROB, MODIF_PROB); symbols = socket_read(socket_fd, &b_packet_size, 1, sa, sa_len, WAITING_TIME, /*ID*/ 3);

}

if (symbols == 0)

return CONNECTION_UNSTABLE;

return (b_packet_size == '+') ? SUCCESS : INVALID_PACKET_SIZE;

}

//Функция загружает файл в пакетном режиме

//Контракт: socket_fd != -1, filename != nullptr, filename имеет '\0',

//1 <= strlen(filename) < FILENAME_LENGTH, 1 <= packet_size <= file_size,

//sa != nullptr, sa_len == sizeof(*sa)

int socket_download_file(const int socket_fd, const char *filename, const long packet_size, const long file_size, struct sockaddr *sa, socklen_t sa_len)

{

FILE *downloaded_file = fopen(filename, "w"); if (!downloaded_file)

{

perror("file open error"); return FILE_OPEN_ERROR;

}

const long n_packets = (file_size + packet_size - 1) / packet_size; // Число пакетов

//Выделяем память под запрос char query[QUERY_SIZE];

//Выделяем память под пакет (на 1 больше для индикации ошибки: + или -) char *packet = (char *)malloc((size_t)(packet_size + 1) * sizeof(char)); for (long i = 0; i < n_packets; ++i)

{

// Формирование запроса на пакет с индексом i snprintf(query, QUERY_SIZE, "PACKET %ld", i); size_t symbols = 0;

packet[0] = '\0';

for (int j = 0; j < ATTEMPTS && (symbols <= 1 || packet[0] != '+'); ++j)

{

// Отправление запроса

socket_write_with_sim(socket_fd, query, strlen(query), sa, sa_len, LOSS_PROB, MODIF_PROB);

// Получение пакета packet[0] = '\0';

symbols = socket_read(socket_fd, packet, (size_t)(packet_size + 1), sa, sa_len, WAITING_TIME, /*ID*/ 4 + i);

}

if (packet[0] == '+' && symbols > 1)

fwrite(packet + 1, symbols - 1, sizeof(char), downloaded_file);

else

return CONNECTION_UNSTABLE;

}

free(packet);

return fclose(downloaded_file) == 0 ? SUCCESS : ERROR_CODE;

}

// Функция для очистки потока ввода void clear_stream(FILE *stream)

{

if (stream)

while (fgetc(stream) != '\n')

25

continue;

}

//Функция ввода y/n

//Контракт: s != NULL, s имеет '\0' int input_yes_or_not(const char *s)

{

int ch;

printf("%s [y/n]: ", s);

while ((ch = fgetc(stdin)) != 'y' && ch != 'n')

{

clear_stream(stdin); // Считывает все оставшиеся ошибочно введенные символы printf("%s [y/n]: ", s); // Приглашает к вводу снова

}

clear_stream(stdin); return ch;

}

//Функция ввода числа типа long из диапазона [from, to]

//Контракт: s != NULL, s имеет '\0'

long input_long(const char *s, long from, long to)

{

if (from > to)

{

long temp = from; from = to;

to = temp;

}

for (long r = 0; /* always true */;)

{

printf("%s [%ld, %ld]: ", s, from, to); scanf("%ld", &r);

clear_stream(stdin);

if (from <= r && r <= to) return r;

}

}

//Функция для конкатенации двух строк s1 + s2

//Контракт: память под строку r должна быть освобождена функцией free(),

//s1 != NULL, s2 != NULL, s1 и s2 имеют '\0'

char *concat(const char *s1, const char *s2)

{

const size_t s1_len = strlen(s1);

char *r = (char *)malloc((s1_len + strlen(s2) + 1) * sizeof(char)); strcpy(r, s1);

strcpy(r + s1_len, s2); return r;

}

int main(void)

{

srand((unsigned int)time(NULL));

//int socket(int domain, int type, int protocol);

//Функция для получения дескриптора конечной точки соединения.

//[1] Параметр domain задает домен соединения: выбирает набор протоколов,

//которые будут использоваться для создания соединения.

//PF_INET -- IPv4 протоколы Интернет.

//[2] Сокет имеет тип type, задающий семантику коммуникации.

//SOCK_DGRAM -- передача датаграмм (ненадежных сообщений с ограниченной длиной

//и не поддерживающих соединения).

//[3] Параметр protocol задает конкретный протокол, который работает с сокетом

//IPPROTO_UDP -- протокол UDP (User Datagram Protocol).

const int socket_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (socket_fd == ERROR_CODE)

{

perror("cannot create socket"); exit(EXIT_FAILURE);

}

struct sockaddr_in sa; // Описывает сокет для работы с протоколами IP const socklen_t sa_len = sizeof(sa); // Размер структуры

struct sockaddr *p_sa = (struct sockaddr *)&sa; // Преобразованный указатель на структуру memset(&sa, 0, sa_len); // Заполнение нулями

sa.sin_family = AF_INET;

//uint16_t htons(uint16_t hostshort); (Host to Network Short)

//Существует два порядка хранения байтов в слове и двойном слове. Один

26

//из них называется порядком хоста (host byte order), другой - сетевым порядком

//(network byte order) хранения байтов. При указании IP-адреса и номера порта

//необходимо преобразовать число из порядка хоста в сетевой.

//В случае uint16_t этим занимается функция htons.

sa.sin_port = htons(PORT);

//in_addr_t inet_addr(const char *cp);

//Функция inet_addr() преобразует обычный вид IP-адреса cp (из номеров и точек)

//в двоичный код в сетевом порядке расположения байтов.

if ((sa.sin_addr.s_addr = inet_addr(HOST)) == INADDR_NONE)

{

perror("inet_addr failed"); exit(EXIT_FAILURE);

}

// Цикл загрузки файлов клиентом char filename[FILENAME_LENGTH + 1]; while (1)

{

// Ввод имени файла

printf("Filename (%d symbols max): ", FILENAME_LENGTH); fgets(filename, FILENAME_LENGTH, stdin);

const size_t len = strlen(filename); if (len <= 1) // Если ничего не введено

{

break;

}

if (filename[len - 1] == '\n') // Если не было выхода за пределы FILENAME_LENGTH

{

filename[len - 1] = '\0';

}

else // Если был выход за пределы FILENAME_LENGTH

{

clear_stream(stdin); // Не считавшиеся данные удаляются

// Вручную добавлять '\0' не нужно (fgets делает это автоматически)

}

// Запрос статуса файла

int status = socket_get_file_status(socket_fd, filename, p_sa, sa_len); if (status == CONNECTION_UNSTABLE)

{

printf("Connection is unstable.\n"); continue;

}

if (status != SUCCESS)

{

printf("File not found\n"); continue;

}

// Запрос размера файла

const long file_size = socket_get_file_size(socket_fd, p_sa, sa_len); if (file_size == CONNECTION_UNSTABLE)

{

printf("Connection is unstable.\n"); continue;

}

if (file_size <= 0)

{

printf("Unknown file size\n"); continue;

}

// Нужно ли загружать файл

if (input_yes_or_not("Download") == 'n') continue;

// Запрос установки размера пакета const long packet_size =

input_long("Packet size", 1L, file_size); // Диапазон: [1, file_size] status = socket_set_packet_size(socket_fd, packet_size, p_sa, sa_len);

if (status == CONNECTION_UNSTABLE)

{

printf("Connection is unstable.\n"); continue;

}

if (status != SUCCESS)

{

printf(status == INVALID_PACKET_SIZE ? "Invalid packet size\n" : "File not found\n"); continue;

}

#if IS_DEBUG == 1

27

printf("\n");

#endif

// Получение пакетов из сокета и запись их в файл

char *new_filename = concat(DOWNLOAD_PREFIX, filename); double start = get_proc_time();

status =

socket_download_file(socket_fd, new_filename, packet_size, file_size, p_sa, sa_len); double stop = get_proc_time();

if (status == CONNECTION_UNSTABLE)

{

printf("Connection is unstable.\n"); free(new_filename);

continue;

}

#if IS_DEBUG == 1 printf("\n");

#endif

printf(status == SUCCESS ? "SUCCESS\n" : "ERROR\n"); printf("Time: %lf seconds\n", stop - start); free(new_filename);

}

// Отправление завершающего соединение сообщения socket_write_with_sim(socket_fd, "X", 1, p_sa, sa_len, LOSS_PROB, MODIF_PROB); close(socket_fd);

return EXIT_SUCCESS;

}

Сборочный файл представлен в таблице 7.

Таблица 7. Сборочный файл

Makefile

.PHONY : clean CC = gcc

CFLAGS = -O0 -std=c11 -Wall -Waddress -Wextra -Werror \ -Wpedantic -Wfloat-equal -Waggregate-return \ -old-style-cast -Wshadow -Wundef -Wpointer-arith \ -Wcast-align -Wcast-qual -fsanitize=address \ -Wstrict-prototypes -Wstrict-overflow=5 \ -Wwrite-strings -Wformat=2 -pedantic-errors \ -Wswitch-default -Wswitch-enum -Wconversion \ -Wunreachable-code -Wredundant-decls -Wenum-compare \ -Wduplicated-branches -Wduplicated-cond -Wlogical-op \ -Wsign-conversion

SOURCES = tcp_client.c tcp_server.c udpgd_client.c udpgd_server.c udp0_client.c udp0_server.c EXECUTABLES = $(SOURCES:.c=)

all: $(SOURCES) $(EXECUTABLES)

.c:

$(CC) $(CFLAGS) $? -lm -o $(?:.c=)

clean:

rm -rf $(EXECUTABLES)

Все используемые в программах заголовочные файлы и функции входят в стандарт POSIX.

Используемый далее в примерах передачи и получения файл file.txt

был сгенерирован Python-скриптом, который представлен в табл. 8.

Таблица 8. Python-скрипт генерации файла

file_gen.py

import string, random

s = string.ascii_letters + string.digits + '\n'

with open('file.txt', 'w') as fp: fp.write(''.join(random.choices(s, k=1_000_000)))

28

Сгенерированный файл состоит из 1.000.000 ascii-символов (аналогично

— байт) латинских букв, цифр и знака перевода строки.

MD5-файла: e866afeef5809ca129b11e22b2741df2.

Работа TCP-сервера и TCP-клиента изображена на рисунке 1.

Рисунок 1. Работа TCP-сервера и TCP-клиента

1.Клиент вводит название файла. Если файл существует, то клиент выбирает — скачивать файл или нет (выбор y/n). Если скачивать, то далее клиент вводит размер пакета (размер пакета — от 1 до размера файла). После скачивания файла появляется статус загрузки и затраченное на загрузку время (скаченный файл имеет префикс

«download_»). Затем процесс повторяется, пока клиент не введет пустое имя файла (это будет означать выход из программы).

2.Программа md5sum вычисляет контрольные суммы MD5 исходного файла (file.txt) и скаченного (download_file.txt). Они совпадают,

т. к. TCP — протокол с гарантированной доставкой данных.

29

Работа UDP0-сервера и UDP0-клиента изображена на рисунке 2.

Рисунок 2. Работа UDP0-сервера и UDP0-клиента

1.В данном случае программа работает аналогично предыдущей, за исключением того, что не гарантирует доставку данных.

2.Программа md5sum вычисляет контрольные суммы MD5 исходного файла (file.txt) и скаченного (download_file.txt). Они совпадают.

Доставка успешна только благодаря тому, что запись и чтение из сокета происходят в одной среде. В более реальных ситуациях пакеты могут теряться, модифицироваться, дублироваться и т. д.

3.Можно заметить тот факт, что версия с UDP (без гарантированной доставки) работает быстрее, чем TCP, хотя результаты загрузки одинаковы — файл доставлен успешно (MD5 совпадает). Это объясняется тем, что TCP обязывает клиента передавать пакет подтверждения (ACK) серверу в случае успешной доставки пакета

(это происходит на нижнем уровне). UDP не обязывает клиента передавать пакет подтверждения серверу, и в предложенном программном варианте UDP0 этот пакет не отправляется, что обеспечивает более быструю передачу, но в то же время не гарантирует доставку и целостность полученных данных.

30