windows tcp 连接上的 recv() 问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5211473/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 16:21:01  来源:igfitidea点击:

Problem with recv() on a tcp connection

c++windowssocketssendrecv

提问by Michael

I am simulating TCP communication on windows in C. I have sender and a receiver communicating.

我正在用 C 语言模拟 Windows 上的 TCP 通信。我有发送方和接收方通信。

The sender sends packets of specific size to the receiver. The receiver gets them and sends an ACK for each packet it received back to the sender. If the sender didn't get a specific packet (they are numbered in a header inside the packet) it sends the packet again to the receiver. Here is the getPacket function on the receiver side:

发送方向接收方发送特定大小的数据包。接收方得到它们,并为它收到的每个数据包发送一个 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;
}

I use firstTimebecause for the first time the receiver doesn't know the normal package size that the sender is going to send to it, so I use a MAX_PACKET_LENGTHto get a package and then set the normal package size to the number of bytes I have received.

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

My problem is the last package. It's size is less than the package size. So lets say last package size is 2 and the normal package size is 4. So recv()gets two bytes, continues to the while condition, then totalChunkLen < packetSizebecause 2<4so it iterates the loop again and the gets stuck in recv()because it's blocking because the sender has nothing to send.

我的问题是最后一个包裹。它的尺寸小于包装尺寸。所以让我们说最后一个包大小是 2,正常包大小是 4。所以recv()得到两个字节,继续 while 条件,然后totalChunkLen < packetSize因为2<4它再次迭代循环并卡住,recv()因为它被阻塞,因为发送者没有什么可发送.

On the sender side I can't close the connection because I didn't get ACK back, so it's kind of a deadlock. The receiver is stuck because it's waiting for more packages but sender has nothing to send.

在发送方,我无法关闭连接,因为我没有收到 ACK,所以这有点僵局。接收方卡住了,因为它正在等待更多的包裹,但发送方没有什么可发送的。

I don't want to use a timeout for recv()or to insert a special character to the package header to mark that it is the last one.

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

What can I do?

我能做什么?

采纳答案by Guy Sirton

You are using TCP to communicate between your receiver and transmitter and TCP is a stream-oriented protocol. That is you put a stream of bytes in one end and you get the stream out on the other end, in order and with no loss. There is no guarantee that each send() will match a recv() on the other end as the data may be broken up for various reasons.

您正在使用 TCP 在接收器和发送器之间进行通信,而 TCP 是面向流的协议。也就是说,您将字节流放在一端,然后在另一端按顺序输出流,不会丢失。无法保证每个 send() 都会匹配另一端的 recv(),因为数据可能会因各种原因而分解。

So if you do the following with a TCP connection:

因此,如果您使用 TCP 连接执行以下操作:

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

And then on the receiver:

然后在接收器上:

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

bytes can be anywhere between 0 and 10 when recv() returns.

当 recv() 返回时,bytes 可以是 0 到 10 之间的任何位置。

TCP runs over IP which is a datagram oriented protocol. This is why the TCP implementation can assume that when it sends a datagram it will receive the entire datagram on the other end (or possibly not, or receive it out-of-order). If you want to simulate that you have at least two options:

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

  1. Add framing to your TCP messages so you can extract packets from it. This involves adding things like the size of the packet to a header that you send into the stream. It would be kind of meaningless to use this for simulating TCP as all your packets would always arrive, always in order and already using the underlying TCP flow control/congestion avoidance mechanisms.
  2. Use a datagram protocol such as UDP. This would be closer to the IP layer that TCP runs over.
  1. 为您的 TCP 消息添加帧,以便您可以从中提取数据包。这涉及将诸如数据包大小之类的内容添加到您发送到流中的标头中。使用它来模拟 TCP 是没有意义的,因为您的所有数据包总是会到达,总是按顺序到达并且已经使用底层 TCP 流控制/拥塞避免机制。
  2. 使用数据报协议,例如 UDP。这将更接近 TCP 运行的 IP 层。

You should probably go with option 2 but if you want to go the framing route over TCP you can e.g. (rough quick code follows):

您可能应该使用选项 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

Receiving end:

收货端:

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

回答by BertV

The concept to keep in mind with low-level socket programming is that you are exchanging a bunch of bytes with no structure imposed by the transport. It is up to you to implement a protocol that does message delineation, either by putting the total length of what you consider a "message" at the start, by using a delimiter byte or sequence which you check in the received buffer, or by closing the connecting at the end (the latter looks easiest but is not the best solution, as you will want to reuse the connection in a real-world program as setting it up is expensive).

使用低级套接字编程要记住的概念是您正在交换一堆字节,而没有传输强加的结构。由您来实现一个进行消息描述的协议,通过将您认为是“消息”的总长度放在开头,通过使用您在接收缓冲区中检查的分隔符字节或序列,或者通过关闭最后的连接(后者看起来最简单但不是最好的解决方案,因为您将希望在实际程序中重用连接,因为设置它很昂贵)。

If this looks to complicated (and it is indeed not always easy), you need to look for a library that encapsulates this work for you, for example allowing you to send and receive an object which will be serialized, delineated and deserialized by the library code. But the work needs to be done and it will not be the transport layer doing it for you.

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

One small remark about the code shown: Your creating a memory leak with your multiple receive buffer allocations...

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

回答by trev

You can specify the amount of data in each packet at the beginning (e.g. the first 2 bytes can specify packet size), or pad the last packet so it's the same size as the others.

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

Edit: If you really want to 'simulate' the TCP then you should probably be using recvfrom() and sendto(), and then you receive the data in whole packets of varying sizes, and you won't have this problem.

编辑:如果您真的想“模拟”TCP,那么您可能应该使用recvfrom() 和sendto(),然后您以不同大小的整个数据包接收数据,您就不会遇到这个问题。

回答by Dave Rager

Your receiver needs to be told by the sender that it has finished. This can be done by either first sending the size of the data the receiver can expect, always send the same amount of data, or send a sentinel value to indicate there will be no more bytes following. The sender could also close the connection when it is finished sending in which case recv will return 0 when there is nothing left to be read and it detects the connection has been closed.

发送者需要告诉您的接收者它已经完成。这可以通过首先发送接收者可以预期的数据大小来完成,始终发送相同数量的数据,或者发送一个标记值以指示后面不会有更多字节。发送方也可以在完成发送后关闭连接,在这种情况下,当没有任何内容可供读取并且检测到连接已关闭时,recv 将返回 0。