在C / C ++中从TCP套接字读取的正确方法是什么?

这是我的代码:

// 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,这是预期的。

你肯定需要更好的error handling – 你可能会泄漏’缓冲区’指向的缓冲区。 我注意到,您在此代码段中没有分配任何内容。

如果你的read()填满整个缓冲区,其他人就你的缓冲区如何不是一个空终止的C字符串提出了一个很好的观点。 这确实是一个问题,也是一个严重问题。

您的缓冲区大小有点小,但只要您不尝试读取超过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’ – >’;’)可以做得更好,这样您只需查看每个传入的字符一次。

说真的,你应该考虑找一个图书馆为你做这一切。 要做到这一点并不容易。

如果按照dirks建议实际创建缓冲区,则:

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

可能会完全填充缓冲区,可能会覆盖提取到字符串流时依赖的终止零字符。 你需要:

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

1)其他人(尤其是dirkgently)已经注意到缓冲区需要分配一些内存空间。 对于小的N值(例如,N <= 4096),您也可以在堆栈上分配它:

 #define BUFFER_SIZE 4096 char buffer[BUFFER_SIZE] 

这样可以避免在发生exception时确保delete[]缓冲区。

但请记住,堆栈大小有限的(堆也是堆栈,但堆栈是finiter),所以你不想在那里放太多。

2)在-1返回代码上,你不应该只是立即返回(立即抛出exception更加粗略。)如果你的代码不仅仅是一个简短的家庭作业,你还需要处理某些正常情况。 。 例如,如果非阻塞套接字上当前没有数据,则可以在errno中返回EAGAIN。 看一下手册(2)。

你在哪里为buffer分配内存? 您调用bzero的行会调用未定义的行为,因为缓冲区未指向任何有效的内存区域。

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

这是我在使用套接字时总是提到的一篇文章。

选择的世界()

它将向您展示如何可靠地使用’select()’并在底部包含一些其他有用的链接,以获取有关套接字的更多信息。