2009-03-20 3 views
18

Вот мой код:Каков правильный способ чтения из сокета TCP в C/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 и C++, как вы можете сказать. BUFFER_SIZE - 256 - должен ли я просто увеличить размер? Если да, то что? Это имеет значение?

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

+1

Это простой C++ ... –

+11

Спасибо за ваш вклад. Обратите внимание, что мой код реализует метод read(), который можно найти в sys/socket.h, которая является «частью библиотеки GNU C», а не библиотекой C++. –

ответ

26

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

Предположим, что ваш заголовок состоит только из 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; 

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

  • Интс имеют одинаковый размер на отправителя и получателя.
  • 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; 

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

+0

Комментарий Олега Пессаха - хороший бесплатный ответ на этот вопрос. – grieve

+0

опоздать на вечеринку, но поскольку вы не знаете Endianess с другой стороны сообщения, длина должна, вероятно, быть в сетевом порядке байтов, поэтому в вашем примере: 'ReadXBytes (socketFileDescriptor, sizeof (length), (void *) (& длина)); длина = :: ntohl (длина); buffer = new char [length]; ReadXBytes (socketFileDescriptor, длина, (void *) buffer); ' –

1

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

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

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

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

Вам нужно обрабатывать возвращаемое значение 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. 
} 

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

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

+0

Не EINPROGRESS. EAGAIN или EWOULDBLOCK. – EJP

3

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

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

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

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

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

THE WORLD OF SELECT()

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

+1

Хотя это теоретически может ответить на вопрос, [было бы желательно] (http://meta.stackexchange.com/q/8259) включить основные части ответа здесь, и укажите ссылку для справки. –

3

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

#define BUFFER_SIZE 4096 
char buffer[BUFFER_SIZE] 

Это экономит вам беспокоиться о гарантируя, что вы delete[] буфер должны быть исключение брошены.

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

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

+0

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

+0

На самом деле, я не обращался к дескриптору открытого сокета, потому что он не был открыт в фрагменте, который вы опубликовали. Но я рад, что ты подумал об этом :-) –

 Смежные вопросы

  • Нет связанных вопросов^_^