Подтвердить что ты не робот

Каков правильный способ чтения из сокета TCP в C/С++?

Здесь мой код:

// Not all headers are relevant to the code snippet.
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <cstdlib>
#include <cstring>
#include <unistd.h>

char *buffer;
stringstream readStream;
bool readData = true;

while (readData)
{
    cout << "Receiving chunk... ";

    // Read a bit at a time, eventually "end" string will be received.
    bzero(buffer, BUFFER_SIZE);
    int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE);
    if (readResult < 0)
    {
        THROW_VIMRID_EX("Could not read from socket.");
    }

    // Concatenate the received data to the existing data.
    readStream << buffer;

    // Continue reading while end is not found.
    readData = readStream.str().find("end;") == string::npos;

    cout << "Done (length: " << readStream.str().length() << ")" << endl;
}

Это немного C и С++, как вы можете сказать. BUFFER_SIZE - 256 - должен ли я просто увеличить размер? Если да, то что? Это имеет значение?

Я знаю, что если "конец" не получен по какой-либо причине, это будет бесконечный цикл, что плохо - так что если бы вы могли предложить лучший способ, пожалуйста, сделайте это.

4b9b3361

Ответ 1

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

Предположим, что ваш заголовок состоит только из 4-байтового целого числа, которое обозначает длину остальной части вашего сообщения. Затем просто выполните следующие действия.

// This assumes buffer is at least x bytes long,
// and that the socket is blocking.
void ReadXBytes(int socket, unsigned int x, void* buffer)
{
    int bytesRead = 0;
    int result;
    while (bytesRead < x)
    {
        result = read(socket, buffer + bytesRead, x - bytesRead);
        if (result < 1 )
        {
            // Throw your error.
        }

        bytesRead += result;
    }
}

Затем в коде

unsigned int length = 0;
char* buffer = 0;
// we assume that sizeof(length) will return 4 here.
ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length));
buffer = new char[length];
ReadXBytes(socketFileDescriptor, length, (void*)buffer);

// Then process the data as needed.

delete [] buffer;

Это делает несколько предположений:

  • ints имеют одинаковый размер для отправителя и получателя.
  • Endianess одинакова как для отправителя, так и для приемника.
  • У вас есть контроль протокола с обеих сторон
  • Когда вы отправляете сообщение, вы можете рассчитать длину спереди.

Поскольку обычно требуется явно знать размер целого, который вы отправляете по сети, определить их в файле заголовка и использовать их явно, например:

// These typedefs will vary across different platforms
// such as linux, win32, OS/X etc, but the idea
// is that a Int8 is always 8 bits, and a UInt32 is always
// 32 bits regardless of the platform you are on.
// These vary from compiler to compiler, so you have to 
// look them up in the compiler documentation.
typedef char Int8;
typedef short int Int16;
typedef int Int32;

typedef unsigned char UInt8;
typedef unsigned short int UInt16;
typedef unsigned int UInt32;

Это изменит сказанное выше на:

UInt32 length = 0;
char* buffer = 0;

ReadXBytes(socketFileDescriptor, sizeof(length), (void*)(&length));
buffer = new char[length];
ReadXBytes(socketFileDescriptor, length, (void*)buffer);

// process

delete [] buffer;

Надеюсь, это поможет.

Ответ 2

Несколько указателей:

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

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

Вам определенно нужна улучшенная обработка ошибок - вы потенциально протекаете буфер, на который указывает "буфер". Который, я заметил, вы не выделяете нигде в этом фрагменте кода.

Кто-то добавил, что ваш буфер не является строкой C с нулевым завершением, если ваш read() заполняет весь буфер. Это действительно проблема и серьезная проблема.

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

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

Что-то вроде этого может сработать для вас:

fd_set read_set;
struct timeval timeout;

timeout.tv_sec = 60; // Time out after a minute
timeout.tv_usec = 0;

FD_ZERO(&read_set);
FD_SET(socketFileDescriptor, &read_set);

int r=select(socketFileDescriptor+1, &read_set, NULL, NULL, &timeout);

if( r<0 ) {
    // Handle the error
}

if( r==0 ) {
    // Timeout - handle that. You could try waiting again, close the socket...
}

if( r>0 ) {
    // The socket is ready for reading - call read() on it.
}

В зависимости от объема данных, которые вы ожидаете получить, способ повторного сканирования всего сообщения для "end"; токен очень неэффективен. Это лучше сделать с помощью конечного автомата (состояния: 'e' → 'n' → 'd' → ';'), чтобы вы только разглядывали каждый входящий символ один раз.

И серьезно, вам стоит подумать о том, чтобы найти библиотеку, чтобы сделать все это для вас. Это не легко понять.

Ответ 3

Если вы действительно создаете буфер в соответствии с предложением dirks, то:

  int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE);

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

  int readResult = read(socketFileDescriptor, buffer, BUFFER_SIZE - 1 );

Ответ 4

1) Другие (особенно dirkgently) отметили, что для буфера необходимо выделить некоторое пространство памяти. Для небольших значений N (скажем, N <= 4096) вы также можете выделить его в стеке:

#define BUFFER_SIZE 4096
char buffer[BUFFER_SIZE]

Это избавит вас от беспокойства о том, чтобы вы delete[] создавали исключение буфера.

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

2) В коде возврата -1 вы не должны просто сразу возвращаться (бросание исключения сразу еще более отрывочно). Существуют определенные нормальные условия, которые вам нужно обрабатывать, если ваш код должен быть чем-то большим, чем короткое домашнее задание. Например, EAGAIN может быть возвращен в errno, если в настоящее время нет данных о неблокирующем сокете. Посмотрите на страницу руководства для чтения (2).

Ответ 5

Где вы выделяете память для своего buffer? Строка, в которой вы вызываете bzero, вызывает поведение undefined, поскольку буфер не указывает на какую-либо допустимую область памяти.

char *buffer = new char[ BUFFER_SIZE ];
// do processing

// don't forget to release
delete[] buffer;

Ответ 6

Это статья, которую я всегда упоминаю при работе с сокетами.

МИР ВЫБРАТЬ()

Он покажет вам, как надежно использовать 'select()' и содержит другие полезные ссылки внизу для дополнительной информации о сокетах.