C: Используя вызов выбора, когда я читаю, как мне отслеживать данные? - PullRequest
1 голос
/ 29 января 2010

Прежде всего, я никогда раньше не работал с C (в основном это Java, поэтому вы можете написать мне наивный C-код). Я пишу простой командный интерпретатор на C. У меня есть что-то вроде этого:

//Initialization code

if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
    perror("Select dead");
    exit(EXIT_FAILURE);
}

....
....
//Loop through connections to see who has the data ready
//If the data is ready
if ((nbytes = recv(i, buf, sizeof(buf), 0)) > 0) {
     //Do something with the message in the buffer
}

Теперь, если я смотрю на что-то вроде длинного абзаца команд, очевидно, что 256-байтовый буфер не сможет получить всю команду. В настоящее время я использую 2056-байтовый буфер для получения всей команды. Но если я хочу использовать 256-байтовый буфер, как мне поступить? Должен ли я отслеживать, какой клиент дал мне какие данные, и добавить их в какой-нибудь буфер? Я имею в виду, использовать что-то вроде двухмерных массивов и тому подобное?

Ответы [ 4 ]

3 голосов
/ 29 января 2010

Да, обычный подход - иметь буфер «данных, которые я получил, но не обработал» для каждого клиента, достаточно большой, чтобы вместить самое большое сообщение протокола.

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

Что-то примерно так:

for (i = 0; i < nclients; i++)
{
    if (!FD_ISSET(client[i].fd, &read_fds))
        continue;

    nbytes = recv(client[i].fd, client[i].buf + client[i].bytes, sizeof(client[i].buf) - client[i].bytes, 0);

    if (nbytes > 0)
    {
        client[i].bytes += nbytes;

        while (check_for_message(client[i]))
        {
            size_t message_len;

            message_len = process_message(client[i]);
            client[i].bytes -= message_len;
            memmove(client[i].buf, client[i].buf + message_len, client[i].bytes);
        }
    }
    else
        /* Handle client close or error */
}

Кстати, вы должны проверить errno == EINTR, если select() возвращает -1, и просто повторить цикл снова - это не фатальная ошибка.

2 голосов
/ 29 января 2010

Я бы держал структуру вокруг для каждого клиента. Каждая структура содержит указатель на буфер, в котором читается команда. Возможно, вы освобождаете буферы, когда они не используются, или, возможно, вы храните их. Структура также может содержать в себе fd клиента. Тогда вам просто нужен один массив (или список) клиентов, который вы зациклите.

Другая причина, по которой вы хотите это сделать, помимо того, что 256 байтов может быть недостаточно, заключается в том, что recv не всегда заполняет буфер. Некоторые данные могут все еще передаваться по сети.

Однако, если вы сохраняете буферы для каждого клиента, вы можете столкнуться с «медленной» атакой, когда один клиент продолжает посылать небольшие биты данных и занимает всю вашу память.

1 голос
/ 29 января 2010

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

Таким образом, вы позволяете ОС управлять клиентскими процессами, то есть вам не нужно иметь структуру данных в вашей программе для управления ими. Вам все равно нужно будет очистить дочерние процессы на вашем сервере, когда они завершатся.

Что касается управления буфером ... Сколько данных вы ожидаете, прежде чем опубликовать ответ? Возможно, вам нужно быть готовым к динамической настройке размера буфера.

1 голос
/ 29 января 2010

Это может быть серьезной болью, когда вы получаете тонны таких данных по сети. Существует постоянная торговля между выделением огромного массива или множественным чтением с перемещением данных. Вы должны рассмотреть возможность получения готового связанного списка буферов, а затем обойти связанный список, когда вы читаете буферы в каждом узле связанного списка. Таким образом, он изящно масштабируется, и вы можете быстро удалить то, что обработали. Я думаю, что это лучший подход, а также то, как boost asio реализует буферизованные чтения.

...