Можно ли зацикливаться на recv / read для чтения всех данных из сокета? - PullRequest
0 голосов
/ 31 октября 2018

Я создаю многопользовательское <-> приложение для обмена сообщениями через TCP. Я создал неблокирующий сервер, используя epoll для мультиплексирования файловых дескрипторов linux.
Когда fd получает данные, я читаю () / или / recv () в buf. Я знаю, что мне нужно либо указать длину данных * в начале передачи, либо использовать разделитель ** в конце передачи для разделения сообщений.

* с использованием длины данных:

char *buffer_ptr = buffer;
        do {
            switch (recvd_bytes = recv(new_socket, buffer_ptr, rem_bytes, 0)) {
                  case -1: return SOCKET_ERR;
                  case 0: return CLOSE_SOCKET;
                  default: break;
            }
            buffer_ptr += recvd_bytes;
            rem_bytes -= recvd_bytes;
        } while (rem_bytes != 0);

** с использованием разделителя:

void get_all_buf(int sock, std::string & inStr)
{
    int n = 1, total = 0, found = 0;
    char c;
    char temp[1024*1024]; 
    // Keep reading up to a '\n'
    while (!found) {
        n = recv(sock, &temp[total], sizeof(temp) - total - 1, 0);
        if (n == -1) {
            /* Error, check 'errno' for more details */
            break;
        }
        total += n;
        temp[total] = '\0';
        found = (strchr(temp, '\n') != 0);
    }
    inStr = temp;
}

Мой вопрос: можно ли циклически повторять recv () до тех пор, пока не будет выполнено одно из этих условий? Что, если клиент отправляет фиктивную длину сообщения или не использует разделитель, или происходит потеря пакета? Не застряну ли я в цикле recv () в моей программе навсегда?

1 Ответ

0 голосов
/ 31 октября 2018

Можно ли циклически выполнять recv (), пока не будет выполнено одно из этих условий?

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

Что если клиент отправит поддельное сообщение длиной

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

или есть потеря пакета?

Если имеется достаточно небольшая потеря пакетов, уровень TCP автоматически выполнит повторную передачу данных, поэтому ваша программа не заметит разницы (кроме сообщения, официально «прибывающего» чуть позже, чем в противном случае) , Если происходит действительно плохая потеря пакетов (например, кто-то выдернул кабель Ethernet из стены на 5 минут), то оставшаяся часть сообщения может быть отложена на несколько минут или более (до тех пор, пока подключение не восстановится или уровень TCP не сработает и не закроется TCP-соединение), задерживая ваш поток в цикле.

Итак, каково решение этой дилеммы промышленного уровня, «злой клиент и ужасная защита от сети», чтобы ваш сервер мог реагировать на действия других клиентов, даже если конкретный клиент не ведет себя сам?

Ответ таков: не полагайтесь на получение всего сообщения сразу. Вместо этого вам нужно настроить простой конечный автомат для каждого клиента, чтобы вы могли recv() столько (или несколько) байтов из TCP-сокета этого клиента, сколько он хочет отправить вам в любое конкретное время, и сохранить эти байты в локальном (для каждого клиента) буфере, который связан с этим клиентом, а затем возвращаются в обычный цикл обработки событий, даже если вы еще не получили все сообщение. Внимательно следите за тем, сколько действительных полученных байтов данных у вас в данный момент имеется под рукой от каждого клиента, и после каждого вызова recv () проверьте, содержит ли связанный буфер данных входящих данных для каждого клиента Целого сообщения пока нет или нет - если это произойдет, проанализируйте сообщение, воздействуйте на него, затем удалите его из буфера. Вспенить, промыть и повторить.

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