通过winsock发送压缩字符串

问题描述 投票:2回答:2

嗨,大家好!我在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;
c++ tcp winsock zlib winsock2
2个回答
0
投票

客户端:

recv只返回任何立即可用的数据或阻塞,直到数据可用,这对于大文件或慢速网络不太可能发生。 recv很可能会阻塞,直到第一个网络数据包到达,并取决于底层网络,可能是从几百字节到几万字节。也许消息适合于那个,也许不适合。

recvflags参数设置为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.


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还是负数。就目前而言,这甚至都没有尝试检测或正确应对大多数网络错误。

© www.soinside.com 2019 - 2024. All rights reserved.