Несколько recv () до времени ожидания? - PullRequest
0 голосов
/ 10 апреля 2020

Итак, я пытаюсь сделать следующее: у меня есть два участника (назовем их A и B), которые общаются через сокет TCP (send () и recv ()). A отправляет счетчик и случайный Nonce, B просто отвечает тем же сообщением, которое получает. Затем A проверяет, соответствует ли ответ отправленному пакету, и если да, увеличивает счетчик и повторяет.

Это фрагмент кода, иллюстрирующий, что A делает в данный момент:

send(sock, payload, strlen(payload), 0);

struct timeval t_out;
t_out.tv_sec = 0;
t_out.tv_usec = 5000;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,&t_out,sizeof(t_out)) <0)

int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);

if (len < 0) 
{
    print("Timeout reached, recv failed: errno %d", errno);
}

else 
{           
    rx_buffer[len] = 0;
    if(strncmp(rx_buffer, payload, payload_len) == 0)
    {       
        pack_nr++;
    }
}

Сейчас Я столкнулся с одной проблемой. Допустим, B по какой-то причине задерживается с ответом. Это вызывает что-то вроде этого:

  1. A отправляет что-то вроде "1xyz"
  2. B имеет задержку ......
  3. A время ожидания и что-то отправляет как "1ab c"
  4. Первый ответ B ("1xyz") достигает A, A решает, что это неправильная полезная нагрузка
  5. Второй ответ B ("1ab c") достигает A тоже, но A выполняет только один recv (), и пока его не видно
  6. A отправляет что-то вроде «1uvw»
  7. A читает «1ab c» из recv () и снова решает, что это неправильная полезная нагрузка
  8. Третий ответ B ("1uvw") достигает A, и так далее и так далее

Итак, я хотел бы поставить recv () в al oop, так что на шаге 5 A сначала будет искать другой ответ от B , пока не истечет время ожидания .

Так есть ли умный способ сделать это? Я думал о чем-то вроде

send(sock, payload, strlen(payload), 0);
int flag = 0;
gettimeofday(&start_time, NULL); 

while((tx_time < start_time + timeout) && flag = 0)
{
    gettimeofday(&tx_time, NULL);
    recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
    if(rx_buffer is okay) 
    {    
        flag = 1;
    }
    wait_a_bit();
}

if(flag == 1) pack_nr++;

Ответы [ 2 ]

0 голосов
/ 11 апреля 2020

Во-первых, вы неправильно проверяете время ожидания. recv() может произойти сбой по ряду причин. Вам нужно проверить errno (или WSAGetLastError() на Windows), чтобы выяснить, ПОЧЕМУ не удалось. Но даже если он действительно потерпел неудачу из-за тайм-аута, TCP является байтовым потоком, задержанные данные могут все еще отображаться (тем более, что 5000 микросекунд (0,005 секунды) слишком короткое время ожидания для надежного использования для TCP сетевой трафик c), но ваш отправитель пошел бы дальше. Единственное разумное, что нужно сделать, если в TCP возникает тайм-аут, это закрыть соединение, так как вы больше не знаете состояние потока.

В вашей ситуации вы в основном реализуете протокол ECHO. Все, что отправляет отправитель, просто возвращается как есть. Таким образом, если вы отправляете 4 байта (которые вы не проверяете, кстати), то вам следует продолжать чтение до тех пор, пока не будут получены 4 байта, а затем сравнить их. Если в этом процессе произойдет сбой, немедленно закройте соединение.

int sendAll(int sock, void *data, int len)
{
    char *ptr = (char*) data;
    while (len > 0) {
        int sent = send(sock, ptr, len, 0);
        if (sent < 0) {
            if (errno != EINTR)
                return -1;
        }
        else {
            ptr += sent;
            len -= sent;
        }
    }
    return 0;
}

int recvAll(int sock, void *data, int len)
{
    char *ptr = (char*) data;
    while (len > 0) {
        int recvd = recv(sock, ptr, len, 0);
        if (recvd < 0) {
            if (errno != EINTR)
                return -1;
        }
        else if (recvd == 0) {
            return 0;
        }
        else {
            ptr += recvd;
            len -= recvd;
        }
    }
    return 1;
}

...

int payload_len = strlen(payload);
if (sendAll(sock, payload, payload_len) < 0)
{
    // error handling
    close(sock);
}
else
{
    struct timeval t_out;
    t_out.tv_sec = 5;
    t_out.tv_usec = 0;

    if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &t_out, sizeof(t_out)) < 0)
    {
        // error handling
        close(sock);
    }
    else
    {
        int res = recvAll(sock, rx_buffer, payload_len);
        if (res < 0) 
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
                print("Timeout reached");
            else
                print("recv failed: errno %d", errno);
            close(sock);
        }
        else if (res == 0)
        {
            print("disconnected");
            close(sock);
        }
        else
        {           
            if (memcmp(rx_buffer, payload, payload_len) == 0)
            {       
                print("data matches");
                pack_nr++;
            }
            else
                print("data mismatch!");
        }
    }
}
0 голосов
/ 10 апреля 2020

"... B просто отвечает тем же сообщением, которое получает. A затем проверяет, соответствует ли ответ отправленному пакету ..."

У вас проблема с кодом и проблема с терминологией.

Во-первых, проблема терминологии: не говорите «соответствует отправленному пакету». Данные могут быть отправлены в одном пакете или десяти пакетах, TCP не волнует. Вы не получаете пакеты, вы получаете данные, которые могут быть разделены или объединены между пакетами по желанию TCP. Это действительно помогает (поверьте мне) быть очень точным в вашем использовании слов. Если вы имеете в виду сообщение, скажите «сообщение». Если вы имеете в виду данные, скажите «данные». Если вы имеете в виду датаграмму, скажите «датаграмма».

К сожалению, ваша проблема с кодом огромна. Вы хотите, чтобы B ответил на A тем же сообщением, которое он получил. Это означает, что вам нужен протокол, который отправляет и получает сообщения. TCP не является протоколом сообщений. Поэтому вам нужно реализовать протокол сообщений и написать код, который фактически отправляет и получает сообщения.

Если A пишет «foobar», B может получить «foobar» или сначала получить «foo», а затем - «bar» ». Если A пишет «foo», то «bar», B может получить «foobar» или «f», а затем «oobar». Это ПТС. Если вам нужен протокол сообщений, вам нужно его реализовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...