Обработка сообщений клиента TCP - PullRequest
4 голосов
/ 04 февраля 2011

Я получаю поток байтов, и мне нужно разделить сообщения, например

Message1\nMessage2\nMessage3\nMess

Каждое сообщение будет добавлено символом '\ n', но когда полное сообщение не помещается в буфер, оно получает часть сообщения и другую его часть при следующем вызове recv, что может потребовать перераспределения памяти для добавления сообщения.

Правильно ли я делаю это или есть лучший способ обработки сообщений вместо перераспределения буфера?

Ответы [ 4 ]

4 голосов
/ 04 февраля 2011

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

, например

int len = 0;
if(recv(socket, reinterpret_cast<char*>(&len), sizeof(int), 0) == sizeof(int))
{
    std::vector<char> buffer;
    buffer.resize(len);

    int bytesRead = 0;
    while(bytesRead < len)
    {
        //read as much as we can. note: byteInc may not == len-bytesRead.
        int byteInc = recv(socket, &buffer[bytesRead], len-bytesRead, 0);
        if(byteInc != SOCKET_ERROR)
        {
            bytesRead += byteInc;
        }
        else
        {
            //should probably handle this error properly
            break;
        }
    }

    //buffer now contains the complete message.
    some_processing_function(buffer);
}
2 голосов
/ 05 февраля 2011

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

Я предоставлю вам хороший код, который сделает это правильно.

На стороне получателя:

unsigned char lenbuf[4];

// This whole thing with the while loop occurs twice here, should probably
// have its own function.
{
    bytesRead = 0;
    while (bytesRead < 4) {
        //read as much as we can. note: byteInc may not == len-bytesRead.
        int byteInc = recv(socket, &lenbuf[bytesRead], 4-bytesRead, 0);
        if(byteInc != SOCKET_ERROR)
        {
            bytesRead += byteInc;
        }
        else
        {
            //should probably handle this error properly
            break;
        }
    }
} // end scope for bytesRead

unsigned int len = ((lenbuf[0] & 0xffu) << 24) | ((lenbuf[1] & 0xffu) << 16)
                   | ((lenbuf[2] & 0xffu) << 8) | (lenbuf[3] & 0xffu);

::std::vector<char> buffer;
buffer.resize(len);

{
    unsigned int bytesRead = 0;
    while(bytesRead < len)
    {
        //read as much as we can. note: byteInc may not == len-bytesRead.
        int byteInc = recv(socket, &buffer[bytesRead], len-bytesRead, 0);
        if(byteInc != SOCKET_ERROR)
        {
            bytesRead += byteInc;
        }
        else
        {
            //should probably handle this error properly
            break;
        }
    }

    //buffer now contains the complete message.
    some_processing_function(buffer);
}

На отправляющей стороне:

const unsigned char lenbuf[4] = {
        ((bytesToSend >> 24) & 0xffu), ((bytesToSend >> 16) & 0xffu),
        ((bytesToSend >> 8) & 0xffu), (bytesToSend & 0xffu)
    };

// This basic block is repeated twice and should be in a function
{
    unsigned int bytesSent = 0;
    while (bytesSend < 4) {
        const int sentNow = send(socket, &lenbuf[bytesSent], 4-bytesSent, 0);
        if (sentNow != SOCKET_ERROR) {
            bytesSent += sentNow;
        } else {
            // Should handle this error somehow.
            break;
        }
    }
}

{
    unsigned int bytesSent = 0;
    while (bytesSent < bytesToSend) {
        const unsigned int toSend = bytesToSend - bytesSent;
        const int sentNow = send(socket, &byteBuf[bytesSent], toSend, 0);
        if (sentNow != SOCKET_ERROR) {
            bytesSent += sentNow;
        } else {
            // Should handle this error somehow.
            break;
        }
    }
}

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

Другая проблема заключается в том, что длина отправляется способом, который не является ЦП инезависимость от компилятора.Различные типы процессоров и разные компиляторы C ++ хранят свои целые числа по-разному.Если комбинация компилятор / ЦП, используемая отправителем, отличается от комбинации компилятор / ЦП, используемой получателем, это вызовет проблемы.

Таким образом, явно разбирая целое число на символы нейтральным по платформе способом и помещая егоснова вместе - лучший путь.

1 голос
/ 04 февраля 2011

В случае, если входящее сообщение очень длинное (~ МБ или ГБ), вы можете использовать буфер постоянной длины и вспомогательную структуру данных, в которой вы будете хранить фрагменты MessageN (N = 1,2 ...).Каждый recv() заполняет буфер с самого начала.Затем вам нужно обработать его содержимое - ищите \n.Если вы найдете его - вы можете извлечь новое сообщение (MessageN);если нет - сохранить содержимое буфера в вспомогательной структуре данных (возможно, вектор или список) и снова выполнить recv().Если вы найдете \n и список не пуст - это означает, что байты до того, как \n станут последним элементом MessageN - объединяют элементы списка и этот фрагмент вместе, а затем очищают список.Если вы нашли \n и список пуст, то это означает, что все байты от начала буфера до \n являются MessageN.Затем вам нужно сохранить в списке байтов после \n (до следующего найденного \n или конца буфера) в качестве первой части сообщения (N + 1).

0 голосов
/ 04 февраля 2011

Если вам не нужно получать сообщение целиком, чтобы начать его обработку, вы также можете использовать кольцевой буфер ( wiki , boost ).

Отправьте первымРазмер хорош, когда я не могу узнать его при запуске, могу ли я предложить вам не использовать unsigned int, так как клиент deflect может заставить вас выделять много памяти (и иметь throw для ограничения длины).

...