Отправителю не приходит ответ от получателя после приема сообщения по UDP (winsock)

Рейтинг: 1Ответов: 1Опубликовано: 30.06.2023

Имеется академическое задание - программа гарантированной доставки файла по ненадежному (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:

  1. Первоначально была проведена отладка, в ходе которой не было выявлено никаких ошибок: программа исправно доходит вплоть до момента вызова recvfrom(6) на М1 и уже только там застывает в вечном ожидании данных.
  2. М1 и М2 работают на одном общем порте - 51000.
  3. Структуры адресов на М1 и М2 корректны, в них лежат одинаковые адреса - это было выяснено в ходе отладки.
  4. В коде, который я приложил к вопросу, нет анализа результатов вызова функций sendto(6) и recvfrom(6), однако в ходе проведения отладки их результаты были учтены - ошибок не выявлено.
  5. WSAGetLastError() не возвращает никакой ошибки.
  6. При использовании для отправления и получения данных одного сокета вместо двух, каждая из машин при вызове recvfrom(6) получает какой-то мусор.
  7. При включении тайм-аута для recvfrom(6) на М1 программа продолжает свою работу, но так никогда и не получает ответа от М2, из-за чего уже теперь она застревает в бесконечном цикле посылания части №0 и получении сообщения о контрольной сумме от М2.
  8. При помещении sendto(6) на М2 даже в бесконечный цикл, М1 никогда не получает ответ о сходимости контрольных сумм.
  9. Вызов ping вместо sendto(6) на М2 и recvfrom(6) на М1 не выявляет никаких ошибок, соединение между машинами есть.

Ответы

▲ 0Принят

У вас код не работает, т.к. вы из функции 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).