7 семестр / Готовые отчеты / ММиВА. Лабораторная работа 2
.pdf
//[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)
//Существует два порядка хранения байтов в слове и двойном слове. Один
//из них называется порядком хоста (host byte order), другой - сетевым
//порядком (network byte order) хранения байтов. При указании IP-адреса
//и номера порта необходимо преобразовать число из порядка хоста в
//сетевой.
//В случае uint16_t этим занимается функция htons.
sa.sin_port = htons(PORT);
// В случае uint32_t этим занимается функция htonl.
sa.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY -- все адреса локального хоста
//Когда сокет создан (с помощью socket), он существует в пространстве имен
//(семействе адресов), но не имеет назначенного адреса. Для назначения
//адреса используют bind.
//int bind(int socket, const struct sockaddr *address, socklen_t address_len);
//Функция для привязки локального адреса address длиной address_len к сокету sockfd. if (bind(socket_fd, p_sa, sa_len) == ERROR_CODE)
{
perror("bind failed"); close(socket_fd); exit(EXIT_FAILURE);
}
//Запросы:
//STATUS <filename> -- получение статуса файла (доступен либо нет)
//SIZE -- получение размера последнего запрошенного файла
//PACKET SIZE <packet_size> -- установка размера пакета (полезной части данных) для передачи
// |
последнего запрошенного |
файла |
// |
PACKET [0 <= i <= n_packets - 1] -- получение пакета |
с индексом i |
const char *q_status_s = "STATUS ", *q_size_s = "SIZE", *q_packet_size_s = "PACKET SIZE ", *q_packet_s = "PACKET ";
const size_t q_status_n = strlen(q_status_s), q_size_n = strlen(q_size_s), q_packet_size_n = strlen(q_packet_size_s), q_packet_n = strlen(q_packet_s);
FILE *requested_file = NULL; // Запрашиваемый файл
char query[QUERY_SIZE + 1], *packet = NULL, filename[FILENAME_LENGTH + 1] = ""; long packet_size = 0, file_size = 0, n_packets = 0;
// Получение сообщений от клиентов while (1)
{
// Чтение запроса
size_t symbols = socket_read(socket_fd, query, QUERY_SIZE, p_sa, sa_len); query[symbols] = '\0';
fflush(stdout);
// Если запрашивается статус файла
if (strncmp(query, q_status_s, q_status_n) == 0)
{
// Сброс
if (requested_file)
fclose(requested_file), requested_file = NULL; free(packet), packet = NULL;
//Сохранение имени запрошенного файла в переменную filename size_t filename_size = symbols - q_status_n; strncpy(filename, query + q_status_n, filename_size); filename[filename_size] = '\0';
//Отправление статуса файла
socket_write(socket_fd, access(filename, F_OK) == 0 ? "+" : "-", 1, p_sa, sa_len);
}
// Если запрашивается размер файла
else if (strncmp(query, q_size_s, q_size_n) == 0)
{
// Сброс
if (requested_file)
fclose(requested_file), requested_file = NULL; free(packet), packet = NULL;
// Попытка открытия файла на чтение
11
if (!(requested_file = fopen(filename, "r")))
{
// Если не получилось, ответ '-' socket_write(socket_fd, "-", 1, p_sa, sa_len); continue;
}
fseek(requested_file, 0L, SEEK_END); // Переход в конец файла
file_size = ftell(requested_file); // Получение текущей позиции в файле fseek(requested_file, 0L, SEEK_SET); // Переход в начало файла
// Отправление размера файла
snprintf(query, FILESIZE_LENGTH, "%ld", file_size); socket_write(socket_fd, query, strlen(query), p_sa, sa_len);
}
// Если запрашивается установка размера пакета и запрошенный файл открыт
else if (requested_file && strncmp(query, q_packet_size_s, q_packet_size_n) == 0)
{
// Сброс
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(socket_fd, "+", 1, p_sa, sa_len);
}
else // Не одобрено
socket_write(socket_fd, "-", 1, p_sa, sa_len);
}
// Если запрашивается пакет, запрошенный файл открыт и память для пакета выделена else if (requested_file && packet && strncmp(query, q_packet_s, q_packet_n) == 0)
{
// Получение индекса пакета
const long packet_i = atol(query + q_packet_n);
if (packet_i < 0 || packet_i >= n_packets) // Если индекс выходит за диапазон
{
// Индикация: - (есть ошибка) socket_write(socket_fd, "-", 1, p_sa, sa_len); 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] = '+';
//Отправление временной строки в сокет
socket_write(socket_fd, packet, symbols, p_sa, sa_len);
}
else
{
if (query[0] == 'X')
{
if (requested_file)
fclose(requested_file), requested_file = NULL; free(packet), packet = NULL;
}
else // Запрос не распознан socket_write(socket_fd, "-", 1, p_sa, sa_len);
}
}
close(socket_fd); return EXIT_SUCCESS;
}
12
Реализация UDP0-клиента на языке C приведена в таблице 4.
Таблица 4. Реализация UDP0-клиента на языке C
udp0_client.c
// udp0_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/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 SUCCESS 0 #define ERROR_CODE -1 #define FILE_NOT_FOUND -2
#define INVALID_PACKET_SIZE -3 #define FILE_OPEN_ERROR -4
// Функция возвращает процессорное время процесса в секундах
double get_proc_time(void) { return (double)clock() / CLOCKS_PER_SEC; }
//Функция отправляет строку в сокет
//Контракт: 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)
{
ssize_t bytes = 0;
if ((bytes = sendto(socket_fd, buffer_in, n * sizeof(char), 0, sa, sa_len)) <= ERROR_CODE)
{
perror("write operation failed"); close(socket_fd); exit(EXIT_FAILURE);
}
return (size_t)bytes / 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)
{
ssize_t bytes = 0;
if ((bytes = recvfrom(socket_fd, buffer, n * sizeof(char), 0, sa, &sa_len)) <= ERROR_CODE)
{
perror("read operation failed"); close(socket_fd); exit(EXIT_FAILURE);
}
return (size_t)bytes / sizeof(char); // Число прочитанных символов
}
//Функция отправляет запрос на получение статуса файла (существует или нет)
//Контракт: 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); socket_write(socket_fd, query, strlen(query), sa, sa_len); socket_read(socket_fd, &b_filename_exists, 1, sa, sa_len); return b_filename_exists == '+' ? SUCCESS : FILE_NOT_FOUND;
}
13
//Функция отправляет запрос на получение размера файла
//Контракт: 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]; socket_write(socket_fd, "SIZE", 4, sa, sa_len);
const size_t symbols = socket_read(socket_fd, file_size, FILESIZE_LENGTH, sa, sa_len); 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); socket_write(socket_fd, query, strlen(query), sa, sa_len); socket_read(socket_fd, &b_packet_size, 1, sa, sa_len);
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); // Отправление запроса
socket_write(socket_fd, query, strlen(query), sa, sa_len);
//Получение пакета packet[0] = '\0';
size_t symbols = socket_read(socket_fd, packet, (size_t)(packet_size + 1), sa, sa_len);
//Запись пакета в файл, если он поступил
if (packet[0] == '+' && symbols > 1)
fwrite(packet + 1, symbols - 1, sizeof(char), downloaded_file);
else
printf("Response error: %.*s\n", (int)symbols, packet);
}
free(packet);
return fclose(downloaded_file) == 0 ? SUCCESS : ERROR_CODE;
}
//Функция для очистки потока ввода void clear_stream(FILE *stream)
{
if (stream)
while (fgetc(stream) != '\n') continue;
}
//Функция ввода y/n
//Контракт: s != NULL, s имеет '\0' int input_yes_or_not(const char *s)
{
int ch;
printf("%s [y/n]: ", s);
14
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)
//Существует два порядка хранения байтов в слове и двойном слове. Один
//из них называется порядком хоста (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)
15
{
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 != SUCCESS)
{
printf("File not found\n"); continue;
}
// Запрос размера файла
const long file_size = socket_get_file_size(socket_fd, p_sa, sa_len); 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 != SUCCESS)
{
printf(status == INVALID_PACKET_SIZE ? "Invalid packet size\n" : "File not found\n"); continue;
}
//Получение пакетов из сокета и запись их в файл
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();
printf(status == SUCCESS ? "SUCCESS\n" : "ERROR\n"); printf("Time: %lf seconds\n", stop - start); free(new_filename);
}
// Отправление завершающего соединение сообщения socket_write(socket_fd, "X", 1, p_sa, sa_len); close(socket_fd);
return EXIT_SUCCESS;
}
16
Под UDPgd будем понимать надпротокол (реализацию) с
гарантированной доставкой (guaranteed delivery) по протоколу UDP.
Реализация UDPgd-сервера на языке C приведена в таблице 5.
Таблица 5. Реализация UDPgd-сервера на языке C
udpgd_server.c
//udpgd_server.c
//Однопоточная реализация для одного клиента
#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 QUERY_SIZE 200
#define FILENAME_LENGTH 50 // не включая '\0' (должен быть меньше, чем QUERY_SIZE) #define FILESIZE_LENGTH 20 // не включая '\0' (должен быть меньше, чем QUERY_SIZE) #define IS_DEBUG 0 // debug-режим (выводит ошибки отправки пакетов)
//Имитация проблем на соединении
//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
//Функция вычисляет контрольную сумму 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, long id)
{
// Размер буфера: 2 байта (16 бит) на контрольную сумму + сам пакет size_t buffer_out_size = sizeof(uint16_t) + sizeof(char) * n;
char *buffer_out = malloc(buffer_out_size);
checksum = (uint16_t)(id + ((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"); close(socket_fd); free(buffer_out);
17
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)
{
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);
return received_checksum == calculate_crc16(buffer, symbols) ? 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, long id)
{
//Создание массива для модификации
//(неконстантная копия аргумента 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 отправлений
//3. Ничего не делать (отправить без ошибок)
//=> 9990 успешных доставок на 10000 отправлений
18
// => 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, id); free(buffer);
fflush(stdout); return 2;
}
// Без имитации
socket_write(socket_fd, buffer, buffer_size / sizeof(char), sa, sa_len, checksum, id); 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, id); free(buffer);
return 0; #endif
}
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)
//Существует два порядка хранения байтов в слове и двойном слове. Один
//из них называется порядком хоста (host byte order), другой - сетевым
//порядком (network byte order) хранения байтов. При указании IP-адреса
//и номера порта необходимо преобразовать число из порядка хоста в
19
//сетевой.
//В случае uint16_t этим занимается функция htons. sa.sin_port = htons(PORT);
//В случае uint32_t этим занимается функция htonl.
sa.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY -- все адреса локального хоста
//Когда сокет создан (с помощью socket), он существует в пространстве имен
//(семействе адресов), но не имеет назначенного адреса. Для назначения
//адреса используют bind.
//int bind(int socket, const struct sockaddr *address, socklen_t address_len);
//Функция для привязки локального адреса address длиной address_len к сокету sockfd. if (bind(socket_fd, p_sa, sa_len) == ERROR_CODE)
{
perror("bind failed"); close(socket_fd); exit(EXIT_FAILURE);
}
//Запросы:
//STATUS <filename> -- получение статуса файла (доступен либо нет)
//SIZE -- получение размера последнего запрошенного файла
//PACKET SIZE <packet_size> -- установка размера пакета (полезной части данных) для передачи
// |
последнего запрошенного |
файла |
// |
PACKET [0 <= i <= n_packets - 1] -- получение пакета |
с индексом i |
const char *q_status_s = "STATUS ", *q_size_s = "SIZE", *q_packet_size_s = "PACKET SIZE ", *q_packet_s = "PACKET ";
const size_t q_status_n = strlen(q_status_s), q_size_n = strlen(q_size_s), q_packet_size_n = strlen(q_packet_size_s), q_packet_n = strlen(q_packet_s);
FILE *requested_file = NULL; // Запрашиваемый файл
char query[QUERY_SIZE + 1], *packet = NULL, filename[FILENAME_LENGTH + 1] = ""; long packet_size = 0, file_size = 0, n_packets = 0;
// Получение сообщений от клиентов while (1)
{
// Чтение запроса
size_t symbols = socket_read(socket_fd, query, QUERY_SIZE, p_sa, sa_len, -1); query[symbols] = '\0';
fflush(stdout);
// Если запрашивается статус файла
if (strncmp(query, q_status_s, q_status_n) == 0)
{
// Сброс
if (requested_file)
fclose(requested_file), requested_file = NULL; free(packet), packet = NULL;
//Сохранение имени запрошенного файла в переменную filename size_t filename_size = symbols - q_status_n; strncpy(filename, query + q_status_n, filename_size); filename[filename_size] = '\0';
//Отправление статуса файла
socket_write_with_sim(socket_fd, access(filename, F_OK) == 0 ? "+" : "-", 1, p_sa, sa_len, LOSS_PROB, MODIF_PROB, /*ID*/ 1);
}
// Если запрашивается размер файла
else if (strncmp(query, q_size_s, q_size_n) == 0)
{
// Сброс
if (requested_file)
fclose(requested_file), requested_file = NULL; free(packet), packet = NULL;
// Попытка открытия файла на чтение
if (!(requested_file = fopen(filename, "r")))
{
// Если не получилось, ответ '-'
socket_write_with_sim(socket_fd, "-", 1, p_sa, sa_len, LOSS_PROB, MODIF_PROB, /*ID*/ 2);
continue;
}
fseek(requested_file, 0L, SEEK_END); // Переход в конец файла
file_size = ftell(requested_file); // Получение текущей позиции в файле fseek(requested_file, 0L, SEEK_SET); // Переход в начало файла
// Отправление размера файла
snprintf(query, FILESIZE_LENGTH, "%ld", file_size); socket_write_with_sim(socket_fd, query, strlen(query), p_sa, sa_len, LOSS_PROB,
MODIF_PROB, /*ID*/ 2);
}
// Если запрашивается установка размера пакета и запрошенный файл открыт
else if (requested_file && strncmp(query, q_packet_size_s, q_packet_size_n) == 0)
20
