recv иногда получает не целые данные - PullRequest
4 голосов
/ 22 декабря 2010

У меня следующая проблема: вот фрагмент кода:

void get_all_buf(int sock, std::string & inStr) {
    int n = 1;
    char c;
    char temp[1024*1024]; 

    bzero(temp, sizeof(temp));

    n = recv(sock, temp, sizeof(temp), 0);

    inStr = temp;
};

, но иногда recv возвращает не целые данные (длина данных всегда меньше sizeof(temp)), только их частьСторона записи всегда отправляет мне целые данные (я получил их с помощью сниффера).Что важно?Спасибо.

PS Знаю, хороший способ предлагает мне проверить n (if (n < 0) perror ("error while receiving data")), но сейчас это не имеет значения - это не причина моей проблемы.*

PS2 Я забыл - это блокировка сокета.

Ответы [ 3 ]

11 голосов
/ 22 декабря 2010

Стандарт TCP допускает фрагментацию пакетов данных. На практике это не происходит с небольшими пакетами данных размером в несколько сотен байтов или около того, но мегабайт данных почти наверняка фрагментируется.

Во-вторых, когда вы говорите, что сниффер говорит, что все данные отправляются, в одном пакете или во многих?

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

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

6 голосов
/ 22 декабря 2010

Гораздо лучший способ - использовать следующее:

void get_all_buf(int sock, std::string & output) {
    char buffer[1024];

    int n;
    while((errno = 0, (n = recv(sock, buffer, sizeof(buffer), 0))>0) || 
          errno == EINTR)
    {
        if(n>0)
            output.append(buffer, n);
    } 

    if(n < 0){
        /* handle error - for example throw an exception*/
    }
};

Также обратите внимание, что буфер, выделенный в стеке, намного меньше.Наличие 1M буфера в стеке может вызвать переполнение стека.

Дополнительное примечание: вы, вероятно, не хотите читать, пока сокет не закрыт, поэтому вам может потребоваться добавить еще одно условие завершения в цикл while.

3 голосов
/ 22 декабря 2010

TCP работает как слой поверх других уровней: IP и Ethernet. IP позволяет фрагментировать данные, а Ethernet позволяет некоторым данным теряться по проводам. Это приводит к потере данных, и это отражается на ваших звонках в recv.

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

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

Например, вы можете использовать "\ n" в качестве разделителя. Этот код может быть улучшен, но я надеюсь, что вы получите идею:

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;
}
...