嗨,大家好!我在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;
答案 0 :(得分:0)
在我看来,您有一个正确的基本概念:发送期望的数据大小,然后发送数据本身。在接收端,首先读取大小,然后读取指定数量的数据。
不幸的是,在实现该意图的细节方面,您犯了一个或两个错误。第一个重要因素是您发送数据的时间:
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或负数。就目前而言,它甚至没有尝试检测大多数网络错误或对其做出适当反应。
答案 1 :(得分:0)
客户端:
recv
仅返回立即可用的任何数据,或者返回直到数据变为可用为止的所有数据,这对于大文件或慢速网络来说不太可能发生。 recv
很可能会阻塞,直到第一个网络数据包到达为止,并取决于可能从几百个字节到数万个字节的基础网络。也许信息适合,也许不适合。
将recv
的{{1}}参数设置为flags
对于短消息很有用,因为您将获得所需的确切字节数或错误。由于可能会出现错误,因此您始终必须测试返回值。
要重复:始终检查返回值。
MSG_WAITALL
的返回值对于套接字故障为负,对于套接字关闭为0,或者为读取的字节数。有关更多信息,请咨询winsock documentation for recv
。
所以...
recv
和 recv(Connection,(char *)&msgl_size,sizeof(unsigned long),NULL);
不检查返回值。套接字可能失败,或者对recv(Connection, (char*)&msg_size, sizeof(unsigned long), NULL);
的调用返回的次数可能少于请求的次数,并且程序的其余部分将在垃圾回收上运行。
这些是使用recv
的好地方,但是可能套接字没有问题,并且您被信号打断了。不知道这是否可以在Windows上发生,但可以在Linux上发生。提防。
MSG_WAITALL
下一步,
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);
在一次调用中返回正确的金额。不太可能,但是如果确实如此,它必须在第一次读取时发生,因为代码每次都会覆盖先前的读取。
说第一次尝试只从套接字读取消息的1/2。由于这不是完整的消息,因此循环进入并尝试再次读取,用消息的后半部分覆盖消息的前半部分,并可能覆盖后续消息中的足够字节以满足请求的字节数。这两个消息的合并不会解密。
对于可能很大的有效负载,请循环运行,直到程序拥有所有内容为止。
recv
减压可能存在问题。我对此不太了解,无法回答。我建议您删除它并发送易于调试的纯文本,直到解决所有网络问题为止。
服务器端:
像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
,s recv
可能会失败。始终检查返回值以查看实际情况。 Check the documentation for send
for more information.