Отправителю не приходит ответ от получателя после приема сообщения по UDP (winsock)
Имеется академическое задание - программа гарантированной доставки файла по ненадежному (UDP) каналу связи.
Реализовано разбиение файла на части (chunk), вычисление контрольной суммы. Дело остается за отправкой, в ней и возникает проблема. Сам я совсем новичок в C++ и уж тем более в программировании сокетов.
Проблема следующая. При запуске программы-отправителя (назовем M1) она посылает информацию о файле программе-получателю (назовем M2), та, в свою очередь, их принимает. После M1 производит отправку первой части файла, M2 принимает её и вычисляет контрольную сумму. Затем M2 отсылает ответ, сошлась ли контрольная сумма, т.е. был ли принят файл корректно. Именно этот ответ и никак не доходит до M1 по непонятной мне причине, после чего программа виснет в вечном ожидании этих данных.
Предупреждаю сразу, код здоровый и костыльный, но больше обратиться за помощью буквально некуда - перечитал всю документацию и все форумы, не могу понять, в чем дело.
Структура части файла (chunk):
struct Chunk {
unsigned int number;
unsigned long int hash;
char* name;
FILE* file;
char* content;
size_t max_size;
size_t size;
};
Небольшая функция-обертка для создания и заполнения структуры адреса:
sockaddr_in getAddressStruct(const short& address_family, const char*& ip_address, const unsigned int& port) {
sockaddr_in address{};
ZeroMemory(&address, sizeof(address));
address.sin_family = address_family;
address.sin_port = htons(port);
address.sin_addr.s_addr = inet_addr(ip_address);
return address;
}
Функция отправки файла:
void sendFile(const char*& file_path, const unsigned int& chunk_amount, const unsigned int& chunk_size, const char*& sender_ip, const char*& receiver_ip, const unsigned short int& port) {
if (WSAStart() != 0) {
fprintf(stderr, "Winsock startup error has been occurred!\n ");
exit(1);
}
SOCKET input_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
SOCKET output_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (input_socket == INVALID_SOCKET || output_socket == INVALID_SOCKET) {
WSACleanup();
fprintf(stderr, "Socket creation error has been occurred!\n ");
exit(1);
}
sockaddr_in sender_address = getAddressStruct(AF_INET, sender_ip, port);
int sender_size = sizeof(sender_address);
sockaddr_in receiver_address = getAddressStruct(AF_INET, receiver_ip, port);
int receiver_size = sizeof(receiver_address);
if (bind(input_socket, (sockaddr*) &sender_address, sender_size) == SOCKET_ERROR) {
closesocket(input_socket);
closesocket(output_socket);
WSACleanup();
fprintf(stderr, "Socket binding error has been occurred!\n ");
exit(1);
}
const char* file_name = getFileName(file_path);
const char* str_chunk_amount = std::to_string(chunk_amount).c_str();
const char* str_chunk_size = std::to_string(chunk_size).c_str();
printf("File info was sent.\n\n");
sendto(output_socket, file_name, strlen(file_name) + 1, 0, (sockaddr*) &receiver_address, receiver_size);
sendto(output_socket, str_chunk_amount, sizeof(str_chunk_amount), 0, (sockaddr*) &receiver_address, receiver_size);
sendto(output_socket, str_chunk_size, sizeof(str_chunk_size), 0, (sockaddr*) &receiver_address, receiver_size);
Chunk chunk_template{};
chunk_template.name = "chunk_";
chunk_template.max_size = chunk_size;
for (unsigned int _ = 0; _ < chunk_amount; _++) {
bool is_received = false;
Chunk chunk = createChunk(chunk_template, _);
chunk.file = fopen(chunk.name, "rb");
chunk.content = new char[chunk.max_size];
chunk.size = fread(chunk.content, 1, chunk.max_size, chunk.file);
chunk.hash = hash(chunk.content);
fclose(chunk.file);
char* str_chunk = chunkToString(chunk);
while (!is_received) {
printf("Sending chunk #%d . . . ", chunk.number);
sendto(output_socket, str_chunk, strlen(str_chunk) + 1, 0, (sockaddr*) &receiver_address, receiver_size);
char* receiver_acknowledgement = new char[64];
recvfrom(input_socket, receiver_acknowledgement, 64, 0, (sockaddr*) &receiver_address, &receiver_size);
printf("Receiver acknowledgement: %s\n", receiver_acknowledgement);
if (!strcmp(receiver_acknowledgement, "SUCCESS")) {
is_received = true;
printf("Success!\n");
}
else
printf("Chunk was corrupted! Resending . . .\n\n");
}
delete[] chunk.content;
}
delete[] file_name;
delete[] str_chunk_amount;
delete[] str_chunk_size;
closesocket(input_socket);
closesocket(output_socket);
WSACleanup();
}
Функция получения файла:
unsigned int receiveFile(const char*& file_path, const char*& receiver_ip, const char*& sender_ip, const unsigned short int& port) {
if (WSAStart() != 0) {
fprintf(stderr, "Winsock startup error has been occurred!\n ");
exit(1);
}
SOCKET input_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
SOCKET output_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (input_socket == INVALID_SOCKET || output_socket == INVALID_SOCKET) {
WSACleanup();
fprintf(stderr, "Socket creation error has been occurred!\n ");
exit(1);
}
sockaddr_in receiver_address = getAddressStruct(AF_INET, receiver_ip, port);
int receiver_size = sizeof(receiver_address);
sockaddr_in sender_address = getAddressStruct(AF_INET, sender_ip, port);
int sender_size = sizeof(sender_address);
if (bind(input_socket, (sockaddr*) &receiver_address, receiver_size) == SOCKET_ERROR) {
closesocket(input_socket);
closesocket(output_socket);
WSACleanup();
fprintf(stderr, "Socket binding error has been occurred!\n ");
exit(1);
}
printf("Ready to receive file!\n");
char* received_file_name = new char[256];
recvfrom(input_socket, received_file_name, 256, 0, (sockaddr*) &sender_address, &sender_size);
char* str_chunk_amount = new char[64];
recvfrom(input_socket, str_chunk_amount, 64, 0, (sockaddr*) &sender_address, &sender_size);
unsigned int chunk_amount = std::stoi((std::string) str_chunk_amount);
delete[] str_chunk_amount;
char* str_chunk_size = new char[64];
recvfrom(input_socket, str_chunk_size, 64, 0, (sockaddr*) &sender_address, &sender_size);
unsigned int chunk_size = std::stoi((std::string) str_chunk_size);
delete[] str_chunk_size;
printf("Received file info:\n"
" File name: %s\n"
" Chunks amount: %d\n"
" Chunks size: %d\n\n",
received_file_name, chunk_amount, chunk_size);
char* file_name = new char[strlen(file_path) + strlen(received_file_name) + 1];
strcpy(file_name, file_path);
strcpy(file_name + strlen(file_path), received_file_name);
delete[] received_file_name;
std::set<unsigned int> received_chunks_numbers;
Chunk chunk_template{};
chunk_template.name = "chunk_";
chunk_template.max_size = chunk_size;
while (received_chunks_numbers.size() != chunk_amount) {
char* str_chunk = new char[chunk_size * 2];
recvfrom(input_socket, str_chunk, chunk_size * 2, 0, (sockaddr*) &sender_address, &sender_size);
unsigned int* separator_positions = new unsigned int[2];
unsigned int count = 0;
for (unsigned int _ = 0; _ < strlen(str_chunk); _++) {
if (str_chunk[_] == '|') {
separator_positions[count] = _;
count++;
}
}
if (count < 2) {
sendto(output_socket, "ERROR", sizeof("ERROR"), 0, (sockaddr*) &sender_address, sender_size);
printf("Chunk was corrupted! Resend requested . . .\n");
delete[] str_chunk;
continue;
}
char* str_chunk_number = new char[16];
char* str_chunk_hash = new char[64];
char* chunk_content = new char[chunk_size];
strncpy(str_chunk_number, str_chunk, separator_positions[0]);
strncpy(str_chunk_hash, str_chunk + separator_positions[0] + 1, separator_positions[1] - separator_positions[0] - 1);
strcpy(chunk_content, str_chunk + separator_positions[1] + 1);
unsigned int chunk_number = std::stoi(std::string(str_chunk_number));
unsigned long int chunk_hash = std::stoi(std::string(str_chunk_hash));
printf("Receiving chunk #%d . . . ", chunk_number);
delete[] str_chunk_number;
delete[] str_chunk_hash;
if (hash(chunk_content) == chunk_hash) {
received_chunks_numbers.insert(chunk_number);
Chunk chunk = createChunk(chunk_template, chunk_number);
chunk.content = chunk_content;
delete[] chunk_content;
chunk.file = fopen(chunk.name, "wb");
fwrite(chunk.content, 1, chunk.size, chunk.file);
fclose(chunk.file);
sendto(output_socket, "SUCCESS", sizeof("SUCCESS"), 0, (sockaddr*) &sender_address, sender_size);
printf("Success!\n");
delete[] separator_positions;
delete[] str_chunk;
}
else {
sendto(output_socket, "ERROR", sizeof("ERROR"), 0, (sockaddr*) &sender_address, sender_size);
printf("Chunk was corrupted! Resend requested . . .\n");
delete[] separator_positions;
delete[] str_chunk;
}
}
closesocket(input_socket);
closesocket(output_socket);
WSACleanup();
return chunk_amount;
}
Очень надеюсь на помощь в любом виде - словесный ответ, ссылка на статью с похожей проблемой или код.
UPD:
- Первоначально была проведена отладка, в ходе которой не было выявлено никаких ошибок: программа исправно доходит вплоть до момента вызова
recvfrom(6)на М1 и уже только там застывает в вечном ожидании данных. - М1 и М2 работают на одном общем порте - 51000.
- Структуры адресов на М1 и М2 корректны, в них лежат одинаковые адреса - это было выяснено в ходе отладки.
- В коде, который я приложил к вопросу, нет анализа результатов вызова функций
sendto(6)иrecvfrom(6), однако в ходе проведения отладки их результаты были учтены - ошибок не выявлено. WSAGetLastError()не возвращает никакой ошибки.- При использовании для отправления и получения данных одного сокета вместо двух, каждая из машин при вызове
recvfrom(6)получает какой-то мусор. - При включении тайм-аута для
recvfrom(6)на М1 программа продолжает свою работу, но так никогда и не получает ответа от М2, из-за чего уже теперь она застревает в бесконечном цикле посылания части №0 и получении сообщения о контрольной сумме от М2. - При помещении
sendto(6)на М2 даже в бесконечный цикл, М1 никогда не получает ответ о сходимости контрольных сумм. - Вызов
pingвместоsendto(6)на М2 иrecvfrom(6)на М1 не выявляет никаких ошибок, соединение между машинами есть.
Ответы (1 шт):
У вас код не работает, т.к. вы из функции receiveFile() шлете сообщение по адресу sender_address, который в фунции sendFile(), соответствует адресу, связанному с сокетом output_socket, а читать в ней вы пытаетесь сокетом input_socket (там у вас к нему привязано чтение с совсем другого порта).
Судя по отсутствию вопросов в чате вы уже отладились, но на всякий случай вот код простого примера для обмена по UDP в обе стороны. (у меня в linux все работает)
Код сервера. Сервер создает UDP сокет, который слушает localhost, port 12345. Он получает в каждом пакете одно число и суммирует эти числа. Если прислали не число, то он отправляет по адресу источника этого пакета вычисленную сумму. По ходу работы в окошке печатается лог.
// udp-s.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#define PRI_ADDR(x) (x).sin_addr.s_addr & 0xff, ((x).sin_addr.s_addr >> 8) & 0xff, ((x).sin_addr.s_addr >> 16) & 0xff, ((x).sin_addr.s_addr >> 24) & 0xff, ntohs((x).sin_port)
#define PRI_ADDR_FMT "%d.%d.%d.%d (port %d)"
// read udp port 12345 on localhost in dot notation
// if packet is any number, add this number to sum
// if not number send this sum to sender
int
main (int ac, char *av[])
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
exit((perror("socket"), 1));
printf("Server: sock %d created\n", sock);
uint32_t ip = inet_addr("127.0.0.1");
struct sockaddr_in my_addr = {AF_INET, htons(12345), ip};
printf("Server: my_addr: " PRI_ADDR_FMT "\n",
PRI_ADDR(my_addr));
if (bind(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)))
exit((perror("bind"), 1));
struct sockaddr_in cli_addr = {0};
socklen_t cli_asize = sizeof(cli_addr); // it is important, or first recvfrom() don't put from-address to the structure
int sum = 0;
char buf[1025];
for (;;) {
ssize_t l = recvfrom(sock, buf, 1024, 0, (struct sockaddr *)&cli_addr, &cli_asize);
if (l < 0)
exit((perror("recvfrom"), 1));
buf[l] = 0;
printf("recv %d bytes <%s> from " PRI_ADDR_FMT "\n",
(int)l, buf, PRI_ADDR(cli_addr));
int v;
if (sscanf(buf, "%d", &v) == 1)
sum += v;
else {
printf("get <%s>, send sum = %d to " PRI_ADDR_FMT "\n",
buf, sum,
PRI_ADDR(cli_addr));
break;
}
}
sprintf(buf, "%d", sum);
ssize_t l = sendto(sock, buf, strlen(buf), 0, (struct sockaddr *)&cli_addr, cli_asize);
if (l < 0)
exit((perror("sendto"), 1));
return puts("End") == EOF;
}
Код клиента, который шлет на localhost, port 12345 несколько пакетов с числам, затем шлет пакет с текстом "get result" и читает от сервера пакет с суммой переданных чисел. Клиент привязывает (bind) к своему сокету порт 12346, но в принципе, это не обязательно, если из кода выбросить блок с комментарием //unnecessary code, то на обмен пакетами это не повлияет.
// udp-c.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
// udp cliend
// send to udp port 12345 on localhost in dot notation
// binds to port 12346
// if packet is any number, server add this number to sum
// if not number server send this sum to client
#define PRI_ADDR(x) (x).sin_addr.s_addr & 0xff, ((x).sin_addr.s_addr >> 8) & 0xff, ((x).sin_addr.s_addr >> 16) & 0xff, ((x).sin_addr.s_addr >> 24) & 0xff, ntohs((x).sin_port)
#define PRI_ADDR_FMT "%d.%d.%d.%d (port %d)"
int
main (int ac, char *av[])
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
exit((perror("socket"), 1));
printf("Cli: sock %d created\n", sock);
uint32_t ip = inet_addr("127.0.0.1");
#if 1 // unnecessary code
struct sockaddr_in my_addr = {AF_INET, htons(12346), ip};
printf("Cli: my_addr: %d.%d.%d.%d (port %d)\n",
PRI_ADDR(my_addr));
if (bind(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)))
exit((perror("bind"), 1));
#endif
struct sockaddr_in serv_addr = {AF_INET, htons(12345), ip};
socklen_t serv_asize = sizeof(serv_addr);
int sum = 0;
char buf[1025];
for (int i = 0; i < 5; i++) {
sprintf(buf, "%d", i + 1);
ssize_t l = sendto(sock, buf, strlen(buf), 0, (struct sockaddr *)&serv_addr, serv_asize);
if (l < 0)
exit((perror("sendto"), 1));
printf("send %d bytes <%s> to " PRI_ADDR_FMT "\n",
(int)l, buf, PRI_ADDR(serv_addr));
}
sprintf(buf, "get result");
ssize_t l = sendto(sock, buf, strlen(buf), 0, (struct sockaddr *)&serv_addr, serv_asize);
if (l < 0)
exit((perror("final sendto"), 1));
l = recvfrom(sock, buf, 1024, 0, (struct sockaddr *)&serv_addr, &serv_asize);
printf("recv %d bytes, serv_asize = %d serv_addr " PRI_ADDR_FMT "\n",
(int)l, (int)serv_asize,
PRI_ADDR(serv_addr));
if (l < 0)
exit((perror("recvfrom"), 1));
buf[l] = 0;
int v = 0;
sscanf(buf, "%d", &v);
printf("result: %d\n", v);
return puts("End") == EOF;
}
Для теста я запускал сначала в одном окне сервер (компиляция gcc -o udp-s udp-s.c), а затем в другом окне клиента (компиляция gcc -o udp-c udp-c.c).