问题与recv()在TCP连接

我正在C模拟windows上的TCP通信。我有发送者和接收者通信。

发送方将特定大小的数据包发送给接收方。 接收者得到它们并发送一个ACK到接收到的每个数据包给发送者。 如果发送者没有得到一个特定的数据包(它们被编号在数据包内的头部),它将数据包再次发送给接收者。 这是接收端的getPacket函数:

//get the next packet from the socket. set the packetSize to -1 //if it's the first packet. //return: total bytes read // return: 0 if socket has shutdown on sender side, -1 error, else number of bytes received int getPakcet(char* chunkBuff, int packetSize, SOCKET AcceptSocket) { int totalChunkLen = 0; int bytesRecv = -1; bool firstTime = false; if(packetSize == -1) { packetSize = MAX_PACKET_LENGTH; firstTime = true; } int needToGet = packetSize; do { char* recvBuff; recvBuff = (char*)calloc(needToGet, sizeof(char)); if(recvBuff == NULL) { fprintf(stderr, "Memory allocation problem\n"); return -1; } bytesRecv = recv(AcceptSocket, recvBuff, needToGet, 0); if(bytesRecv == SOCKET_ERROR) { fprintf(stderr, "recv() error %ld.\n", WSAGetLastError()); totalChunkLen = -1; return -1; } if(bytesRecv == 0) { fprintf(stderr, "recv(): socket has shutdown on sender side"); return 0; } else if(bytesRecv > 0) { memcpy(chunkBuff + totalChunkLen, recvBuff, bytesRecv); totalChunkLen += bytesRecv; } needToGet -= bytesRecv; } while((totalChunkLen < packetSize) && (!firstTime)); return totalChunkLen; } 

我使用firstTime是因为第一次接收者不知道发送者将发送给它的正常包的大小,所以我使用MAX_PACKET_LENGTH来获得一个包,然后将正常的包大小设置为字节数I已收到。

我的问题是最后一个包。 它的尺寸小于包装尺寸。 所以让我们说最后的包大小是2,正常的包大小是4.所以recv()得到两个字节,继续到while条件,然后totalChunkLen < packetSize因为2<4所以它迭代循环和卡在recv()因为它是阻塞的,因为发送者无需发送。

在发送端,我不能closures连接,因为我没有收到ACK,所以这是一种僵局。 接收器被卡住了,因为它正在等待更多的包,但发件人没有任何发送。

我不想为recv()使用超时,或者在包标头中插入特殊字符来标记它是最后一个。

我能做什么?

您正在使用TCP在您的接收器和发送器之间进行通信,TCP是一个面向流的协议。 那就是你把一个字节流在一端,而你在另一端得到流,顺序没有损失。 不能保证每个send()都会匹配另一端的recv(),因为数据可能因为各种原因而被分解。

所以如果你使用TCP连接进行以下操作:

 char buffer[] = "1234567890"; send(socket, buffer, 10, 0); 

然后在接收器上:

 char buffer[10]; int bytes = recv(socket, buffer, 10, 0); 

当recv()返回时,字节可以是0到10之间的任何值。

TCP运行在面向数据报协议的IP上。 这就是为什么TCP实现可以假定当它发送一个数据报的时候,它将在另一端接收整个数据报(或者可能不是,或者不按顺序接收)。 如果你想模拟,你至少有两个选择:

  1. 添加成帧到你的TCP消息,所以你可以从中提取数据包。 这涉及到将数据包的大小添加到发送到流中的标题。 使用它来模拟TCP是没有意义的,因为所有的数据包总是会到达,总是按顺序,并且已经使用了基本的TCP流量控制/拥塞避免机制。
  2. 使用数据报协议,如UDP。 这将更接近TCP运行的IP层。

你可能应该使用选项2,但是如果你想通过TCP进行组帧,你可以例如(粗略的快速代码如下):

 // We do this to communicate with machines having different byte ordering u_long packet_size = htonl(10); // 10 bytes packet send(socket, &packet_size, 4, 0); // First send the frame size send(socket, buffer, 10, 0); // Then the frame 

收到结束:

 u_long packet_size; // Hold the size of received packet int bytes_to_read = 4; // We send 4 bytes on the wire for size and expect 4 int nresult; // hold result of recv() char *psize = &packet_size; // Point to first byte of size while( bytes_to_read ) // Keep reading until we have all the bytes for the size { nresult = recv(socket, psize, bytes_to_read, 0); if(nresult==0) deal with connection closed. bytes_to_read -= nresult; psize += nresult; } packet_size = ntohl(packet_size); // Now we know the packet size we can proceed and read it similar to above 

用低级套接字编程记住的概念是,你正在交换一堆没有传输结构的字节。 实现一个协议来完成消息描述,可以通过在起始处放一个你认为是“消息”的总长度,使用在接收缓冲区中检查的分隔符字节或序列,或者通过关闭最后连接(后者看起来最简单,但不是最好的解决方案,因为您将要重新使用真实世界的程序中的连接,因为设置昂贵)。

如果这看起来很复杂(实际上并不总是那么容易),那么你需要寻找一个为你封装这个工作的库,例如允许你发送和接收一个对象,这个对象将被图书馆序列化,描述和反序列化码。 但是工作需要完成,而传输层不会为你做。

关于所显示的代码的一个小小的评论:你用多个接收缓冲区分配创建一个内存泄漏…

您的接收器需要由发件人告知它已经完成。 这可以通过首先发送接收者可以预期的数据大小,始终发送相同数量的数据,或者发送一个标记值来指示将不再有字节。 当发送完成时,发送者也可以关闭连接,在这种情况下,当没有什么需要读取的时候,recv将返回0,并且它检测到连接已经关闭。

您可以在开头指定每个数据包中的数据量(例如,前2个字节可以指定数据包大小),或者填充最后一个数据包,使其与其他数据包的大小相同。

编辑:如果你真的想“模拟”TCP,那么你应该使用recvfrom()和sendto(),然后你收到整个数据包的大小不等的数据,你不会有这个问题。