【问题标题】:Garbage values and Buffers differences in TCPTCP 中的垃圾值和缓冲区差异
【发布时间】:2014-03-03 08:13:49
【问题描述】:

第一个问题:我对 TCP 中的缓冲区感到困惑。我试图解释我的问题,我阅读了这个文档TCP Buffer,作者说了很多关于 TCP 缓冲区的内容,这很好,对初学者来说是一个很好的解释。我需要知道的是,这个 TCP 缓冲区与我们在基本客户端服务器程序 (Char *buffer[Some_Size]) 中使用的缓冲区相同,还是 TCP 内部保留的一些不同缓冲区?

我的第二个问题是我正在通过套接字从客户端向服务器发送具有前缀长度 (This is data From me) 的字符串数据,当我在控制台上打印我的数据以及我的字符串时,它会打印一些垃圾值也像这样"This is data From me zzzzzz 1/2 1/2....." ?。但是我通过将char *recvbuf = new char[nlength>>3]; nlength 右移到 3 位 来修复它,但为什么我需要这样做呢?

我的第三个问题与第一个问题有关,如果没有像 TCP 缓冲区这样的东西,它只与 Char *buffer[some_size] 有关,那么我的程序使用这种静态内存分配缓冲区和通过使用char *recvbuf = new char[nlength]; 使用动态内存分配缓冲区。简而言之,哪个最好,为什么?

客户代码

int bytesSent;
int bytesRecv = SOCKET_ERROR;
char sendbuf[200] = "This is data From me";

int  nBytes = 200, nLeft, idx;
nLeft = nBytes;
idx = 0;
uint32_t varSize = strlen (sendbuf);
bytesSent = send(ConnectSocket,(char*)&varSize, 4, 0);
assert (bytesSent == sizeof (uint32_t));
std::cout<<"length information is in:"<<bytesSent<<"bytes"<<std::endl;
// code to make sure  all data has been sent
  while (nLeft > 0)
{
    bytesSent = send(ConnectSocket, &sendbuf[idx], nLeft, 0);
    if (bytesSent == SOCKET_ERROR)
    {
      std::cerr<<"send() error: " << WSAGetLastError() <<std::endl;
      break;
    }
    nLeft -= bytesSent;
    idx += bytesSent;
}

 std::cout<<"Client: Bytes sent:"<< bytesSent;

服务器代码:

int bytesSent;
char sendbuf[200] = "This string is a test data from server";
int   bytesRecv;
int idx = 0;
uint32_t  nlength;
int length_received = recv(m_socket,(char*)&nlength, 4, 0);//Data length info
char *recvbuf = new char[nlength];//dynamic memory allocation based on data length info
//code to make sure all data has been received
while (nlength > 0)
{
    bytesRecv = recv(m_socket, &recvbuf[idx], nlength, 0);

    if (bytesRecv == SOCKET_ERROR)
    {
        std::cerr<<"recv() error: " << WSAGetLastError() <<std::endl;
        break; 
    }
    idx += bytesRecv;
    nlength -= bytesRecv;
}

  cout<<"Server: Received complete data is:"<< recvbuf<<std::endl;
  cout<<"Server: Received bytes are"<<bytesRecv<<std::endl;
  WSACleanup();
  system("pause");
  delete[] recvbuf; 
  return 0;

}

【问题讨论】:

  • 检查ntohl(), htonl() 以正确管理消息长度前缀。
  • 我知道它们(ntohl 和 htonl()),但我真的需要它们,因为我的客户端服务器在相同的 Windows 架构上。不是吗?
  • 它们不需要在同一台主机上运行这些东西,但网络编程的目的通常是在不同主机之间交换数据。
  • 试过了,但还是没用 谢谢!

标签: c++ tcp network-programming


【解决方案1】:

您无条件地从客户端发送 200 个字节,但在服务器中您只收到字符串的实际长度,并且该长度包括字符串终止符。

所以首先你没有收到所有发送的数据(这意味着你将填满系统缓冲区),然后你没有正确终止字符串(这会导致“垃圾”输出尝试打印字符串)。

要解决这个问题,在客户端只发送字符串的实际长度(varSize 的值),而在接收服务器中为终止符再分配一个字符,你当然需要添加。

【讨论】:

  • 谢谢!您能否再次查看代码,我删除了您建议更改的功能,因为那是错误的。我现在已经分别在客户端和服务器中完成了 char sendbuf[30]="This is data From me\0" ; char *recvbuf = new char[nlength+1]; 的操作,但仍然是一样的,不知道吗?
  • @user3232405 您不必在客户端的字符串文字中手动添加终止符,它会由编译器自动放置在那里。但是您确实需要发送它(即发送varSize+1字节)手动添加它在服务器中
  • 约阿希姆·皮勒博格:对不起!仍然没有工作。更重要的是,您能否说明其他问题。
  • @user3232405 一个问题 -> 一个答案!
  • @JoachimPileborg 嗯....我现在明白了,我把这个代码recvbuf[nlength]= 0; 放在我的recv() 之后。谢谢!
【解决方案2】:

第一个问题:我对 TCP 中的缓冲区感到困惑。我在尝试着 解释我的问题,我阅读了这个文档 TCP Buffer,作者说 很多关于 TCP 缓冲区的内容,这很好,并且是一个很好的解释 初学者。我需要知道的是这个 TCP 缓冲区与 我们在基本客户端服务器程序中使用的那个(Char *buffer[Some_Size]) 或它的一些不同的缓冲区由 TCP 在内部保存?

当您调用 send() 时,TCP 堆栈会将您的 char 数组中的一些字节复制到内核缓冲区中,并且 send() 将返回它复制的字节数。然后,TCP 堆栈将尽快处理将这些内核字节通过网络传输到其目的地。请务必注意,send() 的返回值保证与您在传递给它的长度参数中指定的字节数相同;它可能会更少。同样重要的是要注意,send() 的返回值确实暗示有很多字节已经到达接收程序;相反,它仅指示内核已从您那里接受并尝试传递的字节数。

同样,recv() 只是将一些字节从内核缓冲区复制到您指定的数组,然后将它们从内核缓冲区中删除。同样,复制的字节数可能少于您要求的数量,并且通常与发送方在任何特定的 send() 调用中传递的字节数不同。 (例如,如果发送者调用 send() 并且他的 send() 返回 1000,这可能会导致您调用 recv() 两次并且每次 recv() 返回 500,或者 recv() 可能会返回 250 四次,或者(1 , 990, 9) 或任何其他你能想到的组合最终加起来是 1000)

我的第二个问题是我正在发送带有前缀的字符串数据 从客户端通过套接字到服务器的长度(这是我的数据),当 我在控制台上打印我的数据以及我的字符串它打印了一些垃圾 值也像这样“这是来自我的数据 zzzzzz 1/2 1/2 .....”? 但是我通过右移 char *recvbuf = new 来修复它 字符[nlength>>3]; nlength 到 3 位,但为什么我需要这样呢?

就像 Joachim 所说,发生这种情况是因为 C 字符串依赖于 NUL 终止字节(即零字节)的存在来指示它们的结束。您正在接收 strlen(sendbuf) 字节,而 strlen() 返回的值不包括 NUL 字节。当接收者的字符串打印例程尝试打印字符串时,它会一直打印,直到稍后在内存中的某个地方(偶然)找到一个 NUL 字节;同时,您可以看到在那之前内存中的所有随机字节。要解决此问题,请将您的发送字节计数器增加到 (strlen(sendbuf)+1),以便也接收 NUL 终止符字节,或者让您的接收器手动将 NUL 字节放在字符串末尾它已接收到字符串的所有字节。任何一种方式都是可以接受的(后一种方式可能会稍微好一点,因为这样接收方不依赖发送方来做正确的事情)。

请注意,如果您的发送方总是发送 200 个字节而不仅仅是字符串中的字节数,那么如果您的接收方想要接收多个块,则它需要始终接收 200 个字节;否则,当它尝试接收下一个块时,它将首先获取所有额外字节(在字符串之后),然后再获取下一个块的发送长度字段。

如果有的话,我的第三个问题与第一个问题相关 没有什么像 TCP 缓冲区,它只是关于 Char *buffer[some_size] 那么我的程序使用这种静态会注意到什么不同 内存分配缓冲区和使用动态内存分配缓冲区 使用 char *recvbuf = new char[nlength];。简而言之,哪个是最好的和 为什么?

就性能而言,它根本没有区别。 send() 和 receive() 并不关心传递给它们的指针是指向堆还是堆栈。

在设计方面,有一些折衷:如果您使用 new,如果您在使用完缓冲区后并不总是调用 delete[],那么您可能会泄漏内存。 (当抛出异常或采用错误路径时,尤其会发生这种情况)。另一方面,将缓冲区放在堆栈上可以保证不会泄漏内存,但堆栈上的可用空间量是有限的,因此一个非常大的数组可能会导致您的程序用完堆栈空间并崩溃。在这种情况下,堆栈上的单个 200 字节数组是没有问题的,所以这就是我要使用的。

【讨论】:

  • 对于第二个问题,它可以通过将recvbuf[nlength]= 0; 放在 recv() 之后。非常感谢!