通过Winsock发送压缩的字符串

时间:2018-09-04 15:52:37

标签: c++ tcp winsock zlib winsock2

嗨,大家好!我在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;

2 个答案:

答案 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.