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

Вот мой код:

// Not all headers are relevant to the code snippet. #include  #include  #include  #include  #include  #include  #include  #include  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 – должен ли я просто увеличить размер? Если да, то что? Это имеет значение?

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

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

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

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

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

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

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

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

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

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

Если вы беспокоитесь о том, чтобы попасть в бесконечный цикл, когда удаленный хост отправляет вам неверное сообщение (потенциальная атака отказа в обслуживании), вы должны использовать select () с таймаутом в сокете для проверки удобочитаемости и только читать if доступны данные, и выйдите из него, если 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» -> «;»), чтобы вы только разглядывали каждый входящий символ один раз.

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

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

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

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

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

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

 #define BUFFER_SIZE 4096 char buffer[BUFFER_SIZE] 

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

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

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

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

 char *buffer = new char[ BUFFER_SIZE ]; // do processing // don't forget to release delete[] buffer; 

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

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

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

  • Ethernet против TCP против IP?
  • Java TCP-сокет: передача данных медленная
  • C # десериализация структуры после ее получения через TCP
  • Как установить интервал прерывания или тайм-аута TCP / IP в Windows XP?
  • Поддерживает ли ServerSocket обратный сокет на произвольном порту?
  • Какова стоимость многих TIME_WAIT на стороне сервера?
  • Двойной маршрутизатор OpenVpn Port Forwarding
  • Использование SO_REUSEADDR?
  • Что вы используете, когда вам нужен надежный UDP?
  • Сколько соединений сокетов может обрабатывать веб-сервер?
  • Минимальный размер части данных сегмента TCP
  • Interesting Posts

    Какова цель «вернуться ждать» на C #?

    Как сохранить пользовательские объекты в NSUserDefaults

    Как добавить несколько файлов шрифтов для одного и того же шрифта?

    В чем разница между HashSet и List ?

    android getlastknownlocation возвращает null

    Как определить мой фактический внешний IP-адрес через командную строку Windows во время VPN

    Переменные привязки от Service / Factory to Controllers

    Как создать контекстное меню для RecyclerView

    ChromeDriver – отключить расширения режима разработки для Selenium WebDriver

    Установите файлы содержимого для «копирования локального: всегда» в пакете nuget

    Лучший способ вызова JSON WebService из .NET Console

    Получить список процессов в Windows с помощью безопасного набора символов

    Не удается получить доступ к диску, разбита таблица разделов

    Как настроить mspaint на Windows Server 2008R2 / Win 7 для запуска с 1-пиксельным холстом или автоматической обрезкой вставленного изображения?

    Преобразовать Windows Filetime на второе место в Unix / Linux

    Давайте будем гением компьютера.