在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()’并在底部包含一些其他有用的链接,以获取有关套接字的更多信息。