嗨,大家好!我在winsock2 lib c ++上有一个简单的TCP服务器和客户端。服务器只发送字符串消息。客户只是接收它们。这里一切都很好。但是,当我使用zlib库来压缩字符串时,数据正在损坏,我无法在客户端上正确接收它们以解压缩。有人能帮我吗?
服务器:
{
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Client connected\n";
int k = rand() % strings.size();
msg = strings[k];
msg_size = msg.size();
msgl_size = msg_size + msg_size*0.1 + 12;
msgl = new unsigned char[msgl_size + 1]{0};
if (Z_OK != compress((Bytef*)msgl,
&msgl_size,
reinterpret_cast<const unsigned char*>(msg.c_str()),
msg.size()))
{
std::cout << "Compression error! " << std::endl;
exit(2);
}
}
std::thread * thread = new std::thread([&newConnection, msgl, msgl_size, msg_size, msg]() {
std::lock_guard<std::mutex> lock(mtx);
send(newConnection, (char*)&msgl_size, sizeof(unsigned long), NULL);
send(newConnection, (char*)&msg_size, sizeof(unsigned long), NULL);
int res;
do {
res = send(newConnection, (char*)(msgl), sizeof(msgl_size), NULL);
}
while (msgl_size != res);
});
客户:
std::lock_guard<std::mutex> lock(mtxx);
unsigned long msgl_size, msg_size;
recv(Connection, (char*)&msg_size, sizeof(unsigned long), NULL);
recv(Connection, (char*)&msgl_size, sizeof(unsigned long), NULL);
unsigned char * msgl = new unsigned char[msgl_size + 1]{0};
int res;
do {
res = recv(Connection, reinterpret_cast<char*>(msgl), msgl_size, NULL);
}
while (msgl_size != res);
char * msg = new char[msg_size + 1];
if (Z_OK == uncompress(reinterpret_cast<unsigned char*>(msg),
&msg_size,
reinterpret_cast<unsigned char*>(msgl),
msgl_size))
{
msg[msg_size] = '\0';
std::cout << msg << std::endl;
std::cout << "Compress ratio: " << msgl_size / (float)msg_size << std::endl;
}
delete[] msgl;
客户端:
recv
只返回任何立即可用的数据或阻塞,直到数据可用,这对于大文件或慢速网络不太可能发生。 recv
很可能会阻塞,直到第一个网络数据包到达,并取决于底层网络,可能是从几百字节到几万字节。也许消息适合于那个,也许不适合。
将recv
的flags
参数设置为MSG_WAITALL
对于较短的消息非常有用,因为您将获得所要求的字节数或错误。由于存在错误的可能性,您始终必须测试返回值。
重复:始终检查返回值。
recv
的返回值是套接字故障为负,套接字关闭为0或读取的字节数。有关更多信息,请咨询winsock documentation for recv
。
所以...
recv(Connection, (char*)&msg_size, sizeof(unsigned long), NULL);
和recv(Connection,(char *)&msgl_size,sizeof(unsigned long),NULL);
不检查返回值。套接字可能已经失败,或者对recv
的调用可能返回的次数少于请求的数量,程序的其余部分将在垃圾上运行。
这是一个使用MSG_WAITALL
的好地方,但是套接字可能很好并且你被信号打断了。不确定这是否可以在Windows上发生,但它可以在Linux上发生。谨防。
if (recv(Connection, (char*)&msg_size, sizeof(unsigned long), MSG_WAITALL) != sizeof(unsigned long) &&
recv(Connection, (char*)&msgl_size, sizeof(unsigned long), NULL) != sizeof(unsigned long)(
{
// log error
// exit function, loop, or whatever.
}
下一个,
do {
res = recv(Connection, reinterpret_cast<char*>(msgl), msgl_size, NULL);
} while (msgl_size != res);
将循环,直到一个recv
在一次调用中返回正确的金额。不太可能,但如果确实如此,它必须在第一次读取时发生,因为代码每次都会写入先前的读取。
假设第一次尝试时只从套接字读取了1/2的消息。由于这不是完整的消息,循环进入并尝试再次读取,用后半部分覆盖消息的前半部分,并且可能从后续消息中写入足够的字节以满足所请求的字节数。这两个消息的混合不会解密。
对于可能很大的有效载荷,循环直到程序拥有它。
char * bufp = reinterpret_cast<char*>(msgl);
int msg_remaining = msgl_size;
while (msg_remaining )
{
res = recv(Connection, bufp, msg_remaining, NULL);
if (res <= 0)
{
// log error
// exit function, loop, or whatever.
}
msg_remaining -= res; // reduce message remaining
bufp += res; // move next insert point in msgl
}
减压可能存在问题。我不太清楚能够回答这个问题。我建议删除它并发送易于调试的明文,直到你解决了所有的网络问题。
服务器端:
像recv
一样,send
发送它可以。您可能必须循环发送以确保没有溢出套接字,并且消息太大而无法一次性吃掉套接字。再次像recv
,ssend
可能会失败。始终检查返回值以查看实际发生的情况。 Check the documentation for send
for more information.
在我看来,你有正确的基本想法:发送数据的大小,然后是数据本身。在接收方,首先读取大小,然后读取指定数量的数据。
不幸的是,在实现该意图的细节方面,您犯了一两个错误。第一个重要的是你发送数据:
do {
res = send(newConnection, (char*)(msgl), sizeof(msgl_size), NULL);
}
while (msgl_size != res);
这有几个问题。首先,它使用sizeof(msg1_size)
,所以它只是尝试发送无符号长的大小(至少我猜测msg1_size
是无符号长)。
我非常确定你的意思是发送整个缓冲区:
unsigned long sent = 0;
unsigned long remaining = msg1_size;
do {
res = send(newConnection, (char*)(msgl + sent), remaining, NULL);
sent += res;
remaining -= res;
} while (msgl_size != sent);
有了这个,我们开始从缓冲区的开头发送。如果send
仅在发送部分内容后返回(因为它被允许),我们会记录发送了多少内容。然后在下一次迭代中,我们从它停止的位置重新开始发送。同时,我们会跟踪要发送的剩余数量,并且只尝试在每次后续迭代中发送那么多。
至少乍一看,看起来你的接收循环可能需要大致相同类型的修复,跟踪收到的总数而不是试图等待单个传输全部金额。
哦,当然对于真正的代码,你也想检查res
为0还是负数。就目前而言,这甚至都没有尝试检测或正确应对大多数网络错误。