Программирование сокетов, проверка входа, буфер UDP пуст или нет? - PullRequest
2 голосов
/ 16 октября 2011

Запись UDP-клиента, который отправляет строку на сервер. В приведенном ниже коде, когда сервер отправляет обратно несколько пакетов, поведение программы не соответствует моим ожиданиям. Я хочу обработать любой входящий пакет по process() один за другим , пока буфер ввода не станет пустым. Я думаю, что проблема связана с поведением блокировки recv.

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <winsock.h>

using namespace std;

void process(const char *in, int size)
{
    fprintf(stdout, "%s\n", in);
}

int main()
{
    char quack_addr[] = "127.0.0.1";
    unsigned short quack_port = 9091;

    WSAData data;
    WSAStartup(MAKEWORD(2, 2), &data);

    sockaddr_in qserver;
    qserver.sin_family = AF_INET;
    qserver.sin_addr.s_addr = inet_addr(quack_addr);
    qserver.sin_port = htons(quack_port);
    SOCKET client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if (client <= 0)
    {
        fprintf(stderr, "Error - Can not create socket.\n");
        exit(1);
    }

    while (true)
    {
        const int MAX = 1024;
        char sbuf[MAX];
        char rbuf[MAX];

        fprintf(stdout, ": ");
        fgets(sbuf, MAX, stdin);
        int slen = strlen(sbuf);

        int r = sendto(client,sbuf,slen,0,(sockaddr*)&qserver,sizeof(qserver));

        // Current code:
        // int rlen = recv(client, rbuf, MAX, 0);
        // if (rlen > 0)
        // {
        //     rbuf[rlen] = 0;
        //     process(rbuf, rlen);
        // }

        // Question starts here:
        //
        // While there is data in queue do:
        // {
        //    (break if there is no data)
        //    int rlen = recv(client, rbuf, MAX, 0);
        //    rbuf[rlen] = 0;
        //    process(rbuf, rlen);
        // }   
    }

    return 0;
}

Как я могу проверить, пуст буфер или нет, перед вызовом recv(...)?

Проблема возникает в этом сценарии:

  1. Пользователь вводит команду в клиентской программе (cmd1).
  2. Одновременно сервер отправляет 3 пакета клиенту (pkt1, pkt2, pkt3).
  3. После нажатия Enter на стороне клиента я ожидаю получить эти 3 пакета и, возможно, результат, соответствующий cmd1 и process() всем по одному.
  4. Но после нажатия Enter на этапе 3 я получаю pkt1! и после отправки другой команды на сервер я получу pkt2 и так далее ...!

Я знаю, что моего кода недостаточно для решения этой проблемы, поэтому у меня вопрос, как его решить?

Примечание. Я использую netcat -L -p 9091 -u в качестве сервера UDP

Ответы [ 3 ]

2 голосов
/ 16 октября 2011

Я думаю, что проблемы (неудовлетворительное поведение, которое вы не описываете) происходят из другого источника. Позвольте мне перечислить некоторые идеи и комментарии c. / Что было сказано ранее:

(1) блоки recvfrom () тоже. Тем не менее, вы хотите использовать его. Ваше сообщение в настоящее время отправляет и получает от обратной связи, что хорошо для вашей игрушечной программы (но: см. Ниже). При получении данных UDP с помощью recv () вы не знаете, кто их отправил, поскольку сокет никогда не был подключен (). Используйте recvfrom (), чтобы подготовиться к минимальной проверке ошибок в более серьезной программе

(2), так как select () приостанавливает работу программы до ввода / вывода, это только поставит любую проблему с блокировкой сокета на другой уровень. Но это не проблема

(3), чтобы проверить, пуст ли приемный буфер, используйте флаг MSG_PEEK в recvfrom () в соответствующей позиции. Обычно он используется только для работы с дефицитом памяти, но он должен выполнять свою работу.

(4) причина 1, почему я считаю, что вы видите проблемы, которые вы не описали более подробно: UDP-дейтаграммы сохраняют границы сообщений. Это означает, что recvfrom () будет считывать весь кусок данных, составляющих любое отправленное сообщение. Однако, если буфер, в который вы его читаете, меньше, чем считанные данные, любой излишек будет отброшен. Поэтому убедитесь, что у вас большой буфер (в идеале 65 тыс.).

(5) причина 2: Вы получаете любые данные, отправленные в петлю. Если вы в настоящее время также подключены к какой-либо сети (спутниковая сеть, Интернет), то, что вы ловите, может быть из другого источника, чем вы ожидаете. Поэтому, по крайней мере, в фазе покоя, отключите.

Блокировка не должна быть проблемой. Ваша основная логика, когда она написана правильно: Recvfrom () (блокировка / ожидание до готовности) Процесс Peek, если буфер пуст Выход если да Вернитесь назад, чтобы получить больше, если нет,

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

Setsockopt () для optName SO_RCVBUF

2 голосов
/ 16 октября 2011

Используйте select() (с подходящим тайм-аутом) для проверки входящих данных до вызова recv().

Что-то вроде следующего (непереносимый код)

#include <winsock2.h>

...

/* somewhere after your sendto, or your first recv */
fd_set recv_set;
timeval tv = {1, 0}; /* one second */
FD_ZERO(&recv_set);
FD_SET(client, &recv_set);
while (select(0, &recv_set, NULL, NULL, &tv) > 0)
{
    /* recv... */
    FD_SET(client, &recv_set); /* actually redundant, since it is already set */
}
1 голос
/ 16 октября 2011

iPhone иногда выдает ошибку и не позволяет мне оставлять комментарии. Спасибо, Стив. Это просто продолжение разговора.

Я предполагаю, что это означает «раскомментирование» вопроса начинается здесь. Частичный ответ, поскольку это все еще зависит от моего второго комментария; это больше или меньше, чем ожидать. Предполагая, что три сообщения, которые должны быть отправлены сервером, уже поставлены в очередь, после первого нажатия клавиши ВВОД ваш пакет отправляется (никогда не блокируется, так как sendto () не блокируется для UDP), принимается сервером и (I Предположим, см. выше, отозвался и добавлен в буфер приема FIFO, в котором вы уже прочитали три сообщения в очереди. Затем в вашей программе есть recv (), которая получает первое сообщение в очереди и распечатывает его. вернуться к началу цикла, ожидать другого ввода и ждать его (так что это не блокируется на уровне сокета, но когда ваша программа запрашивает ввод, например, просто 'enter'), затем приходит ко второму первоначально отправленному сообщению (посредством сервер) и обрабатывает этот один. Еще один цикл, и все три выполнены. Нажмите Enter еще раз, и, предполагая, что сервер повторяет то, что вы отправили, вы должны начать получать введенные сообщения (которые могут быть пустыми, если вы нажмете только Enter). Цикл в настоящее время не завершится, если вы не убьете его.

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