First question :我对TCP中的缓冲区感到困惑 . 我试图解释我的问题,我读了这篇文章TCP Buffer,作者说了很多关于TCP缓冲区,这很好,对初学者来说是一个非常好的解释 . 我需要知道的是,这个TCP缓冲区与我们在基本客户端服务器程序( Char *buffer[Some_Size]
)中使用的缓冲区是相同的缓冲区,还是TCP内部保留的一些不同的缓冲区?
我的 second question 是我从客户端通过套接字向服务器发送前缀长度( This is data From me
)的字符串数据,当我在控制台上打印我的数据时,我的字符串打印出一些垃圾值也像这样 "This is data From me zzzzzz 1/2 1/2....."
?.但是我通过右移 char *recvbuf = new char[nlength>>3];
nlength to 3 bits 修复了它,但为什么我需要这样做呢?
我的 third question 与第一个问题有关,如果没有像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;
}
2 回答
当您调用send()时,TCP堆栈会将char数组中的一些字节复制到内核缓冲区中,send()将返回它复制的字节数 . 然后,TCP堆栈将尽可能快地处理那些内核中字节到网络目的地的传输 . 's important to note that send()'的返回值不保证与您在传递给它的长度参数中指定的字节数相同;它可能会更少 . 它's also important to note that sends()'的返回值并不意味着很多字节已经到达接收程序;相反,它只表示内核已从您接受并将尝试传递的字节数 .
同样,recv()只是将一些字节从内核缓冲区复制到您指定的数组,然后将它们从内核缓冲区中删除 . 同样,复制的字节数可能小于您要求的数量,并且通常与发送方在任何特定send()调用时传递的字节数不同 . (例如,如果发送方调用send()并且其send()返回1000,则可能导致您调用recv()两次并且recv()每次返回500,或者recv()可能返回250四次,或者(1) ,990,9),或者你能想到的任何其他组合,最终总计达1000)
像Joachim所说,这是因为C字符串依赖于NUL终结符字节(即零字节)的存在来指示它们的结束 . 您正在接收strlen(sendbuf)字节,并且strlen()返回的值不包含NUL字节 . 当接收者的字符串打印例程尝试打印字符串时,它会一直打印,直到在内存中稍后某处找到NUL字节(偶然);与此同时,您可以在该点之前看到内存中的所有随机字节 . 要解决这个问题,可以将发送字节计数器增加到(strlen(sendbuf)1),这样也可以接收NUL终结器字节,或者让接收器手动将NUL字节放在字符串结尾处 . 已收到字符串的所有字节 . 无论哪种方式都是可以接受的(后一种方式可能稍微优选,因为接收方不依赖发送方来做正确的事情) .
请注意,如果您的发送方将始终发送200个字节而不仅仅是字符串中的字节数,那么如果接收方想要接收多个块,则需要始终接收200个字节;否则当它试图接收下一个块时,它将首先获得所有额外的字节(在字符串之后),然后才能获得下一个块的发送长度字段 .
在性能方面,它完全没有区别 . send()和receive()不关心是否有传递给它们的指针指向堆或堆栈 .
在设计方面,有一些权衡:如果使用new,如果在完成缓冲区后并不总是调用delete [],则有可能泄漏内存 . (当抛出异常或采用错误路径时,尤其会发生这种情况) . 另一方面,将缓冲区放在堆栈上保证不会泄漏内存,但堆栈上可用的空间量是有限的,因此一个非常大的数组可能导致程序耗尽堆栈空间并崩溃 . 在这种情况下,堆栈上的单个200字节数组没有问题,所以这就是我要使用的 .
您无条件地从客户端发送200个字节,但在服务器中您只收到字符串的实际长度,并且该长度不包括字符串终止符 .
首先,你没有收到所有发送的数据(这意味着你将填满系统缓冲区),然后你没有正确终止字符串(这会导致在尝试打印字符串时出现“垃圾”输出) .
要解决这个问题,在客户端只发送字符串的实际长度(
varSize
的值),并在接收服务器中为终结器分配一个字符,当然需要添加 .