【问题标题】:Sending and receiving compressed data over a TCP socket通过 TCP 套接字发送和接收压缩数据
【发布时间】:2014-07-25 22:33:39
【问题描述】:

在通过 TCP 套接字发送和接收压缩数据方面需要帮助。

如果我不使用压缩,代码工作得非常好,但是当我使用压缩时会发生一些非常奇怪的事情。基本上,问题是 stream.Read() 操作被跳过,我不知道为什么..

我的代码:

using (var client = new TcpClient())
{
    client.Connect("xxx.xxx.xx.xx", 6100);
    using (var stream = client.GetStream())
    {
        // SEND REQUEST
        byte[] bytesSent = Encoding.UTF8.GetBytes(xml);

        // send compressed bytes (if this is used, then stream.Read() below doesn't work.
        //var compressedBytes = bytesSent.ToStream().GZipCompress();
        //stream.Write(compressedBytes, 0, compressedBytes.Length);

        // send normal bytes (uncompressed)
        stream.Write(bytesSent, 0, bytesSent.Length);

        // GET RESPONSE
        byte[] bytesReceived = new byte[client.ReceiveBufferSize];
        // PROBLEM HERE: when using compression, this line just gets skipped over very quickly
        stream.Read(bytesReceived, 0, client.ReceiveBufferSize);

        //var decompressedBytes = bytesReceived.ToStream().GZipDecompress();
        //string response = Encoding.UTF8.GetString(decompressedBytes);

        string response = Encoding.UTF8.GetString(bytesReceived);

        Console.WriteLine(response);
    }
}

您会注意到上面的一些扩展方法。这是代码,以防您想知道那里是否有问题。

public static MemoryStream ToStream(this byte[] bytes)
{
    return new MemoryStream(bytes);
}


public static byte[] GZipCompress(this Stream stream)
{
    using (var memoryStream = new MemoryStream())
    {
        using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress))
        {
            stream.CopyTo(gZipStream);
        }
        return memoryStream.ToArray();
    }
}

public static byte[] GZipDecompress(this Stream stream)
{
    using (var memoryStream = new MemoryStream())
    {
        using (var gZipStream = new GZipStream(stream, CompressionMode.Decompress))
        {
            gZipStream.CopyTo(memoryStream);
        }
        return memoryStream.ToArray();
    }
}

这些扩展在以下情况下运行良好,所以我确信它们不是问题:

string original = "the quick brown fox jumped over the lazy dog";
byte[] compressedBytes = Encoding.UTF8.GetBytes(original).ToStream().GZipCompress();
byte[] decompressedBytes = compressedBytes.ToStream().GZipDecompress();
string result = Encoding.UTF8.GetString(decompressedBytes);
Console.WriteLine(result);

有谁知道为什么在发送的字节被压缩时会跳过 Read() 操作?

编辑

在向 API 提供商展示上述示例代码后,我收到了一条消息。他们有话要说:

乍一看,我猜标题丢失了。输入必须开始 带有“c”,后跟输入的长度 (在我们的示例中为 sprintf(cLength,"c%09d",hres))。我们需要这个,因为 在我们找到一个二进制 0 来识别结尾之前,我们无法读取。

他们之前在C中提供了一些示例代码,我也不是100%完全看懂,如下:

example in C:

#include <zlib.h>

uLongf hres;
char cLength[COMPRESS_HEADER_LEN + 1] = {'\0'};

n = read(socket,buffer,10);
// check if input is compressed
if(msg[0]=='c') {
     compressed = 1;
}

n = atoi(msg+1);
read.....


hres = 64000;
res = uncompress((Bytef *)msg,   &hres, (const Bytef*) 
buffer/*compressed*/, n);
if(res == Z_OK && hres > 0 ){
     msg[hres]=0; //original
}
else // errorhandling

hres = 64000;

if (compressed){
res = compress((Bytef *)buffer,   &hres, (const Bytef *)msg, strlen(msg));
     if(res == Z_OK && hres > 0 ) {
         sprintf(cLength,"c%09d",hres);
         write(socket,cLength,10);
         write(socket, buffer, hres);
     }
     else // errorhandling

makefile: add "-lz" to the libs

他们正在使用 zlib。我不怀疑这有什么不同,但我确实尝试过使用 zlib.net,但我仍然没有得到任何回应。

谁能给我一个例子,说明我应该如何在 C# 中发送这个输入长度?

编辑 2

作为对@quantdev 的回应,这是我现在正在尝试的长度前缀:

using (var client = new TcpClient())
{
    client.Connect("xxx.xxx.xx.xx", 6100);
    using (var stream = client.GetStream())
    {
        // SEND REQUEST
        byte[] bytes = Encoding.UTF8.GetBytes(xml);
        byte[] compressedBytes = ZLibCompressor.Compress(bytes);

        byte[] prefix = Encoding.UTF8.GetBytes("c" + compressedBytes.Length);

        byte[] bytesToSend = new byte[prefix.Length + compressedBytes.Length];
        Array.Copy(prefix, bytesToSend, prefix.Length);
        Array.Copy(compressedBytes, 0, bytesToSend, prefix.Length, compressedBytes.Length);

        stream.Write(bytesToSend, 0, bytesToSend.Length);

        // WAIT
        while (client.Available == 0)
        {
            Thread.Sleep(1000);
        }

        // GET RESPONSE
        byte[] bytesReceived = new byte[client.ReceiveBufferSize];
        stream.Read(bytesReceived, 0, client.ReceiveBufferSize);

        byte[] decompressedBytes = ZLibCompressor.DeCompress(bytesReceived);
        string response = Encoding.UTF8.GetString(decompressedBytes);

        Console.WriteLine(response);
    }
}

【问题讨论】:

  • 跳过是什么意思?调用后接收到的字节长度是多少?
  • 我的意思是跳过是它只是立即执行该行并移动到下一行。如果我不使用压缩,应用会在stream.Read() 处等待大约 60 秒左右,直到有数据继续运行
  • 您应该在继续之前验证 Read() 调用的返回值。另外,您是否考虑过异步客户端? msdn.microsoft.com/en-us/library/bbx2eya8.aspx

标签: c# sockets tcp compression


【解决方案1】:

您需要检查return value of the Read() calls you are making on the TCP stream:它是有效读取的字节数。

MSDN 说:

返回值

读入缓冲区的总字节数。如果没有那么多字节,这可能小于请求的字节数 当前可用,如果流的结尾已经结束,则为零 (0) 到达。

  • 如果套接字关闭,调用将立即返回 0(这就是这里可能发生的情况)。
  • 如果不为 0,那么您必须检查您实际收到了多少字节,如果小于 client.ReceiveBufferSize,您将需要额外调用 Read 来检索剩余的字节.

在调用读取之前,检查套接字上的某些数据实际上是available

while(client.Available == 0)
// wait ...

http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.available%28v=vs.110%29.aspx

【讨论】:

  • 感谢@quantdev,但使用while(client.Available == 0) 似乎只是让它进入无限循环。我等了大约 3 分钟才放弃..
  • 是的,因为没有人向您的套接字发送任何内容。这就是为什么您需要检查它的原因:)(并且为了避免那些轮询循环,请使用异步套接字)
  • 谢谢 - 如果数据被压缩,API 提供者似乎没有发送任何内容,而且我似乎需要随请求发送一些输入长度。请在上面的主要帖子中查看我的编辑,如果您能提供帮助,那就太好了。
  • @Matt,在 bytesSentArray 前面加上“c”字符,后跟 bytesSent 的长度(作为无符号长整数)(在前面)。这基本上就是他们问你的。顺便说一句,我对您的第一个问题的第一个答案最终是正确的,不是吗? ; )
  • 关于你的第一个答案,我不确定它是否正确,因为没有压缩它工作正常.. 发生的事情是(因为它是同步的)它只是在等待响应(通常约 60 秒)然后继续。我不需要特别告诉它等待。但无论如何我已经实施了这个建议.. 但就像我说的我不知道它是否正确,因为当我用压缩数据测试它时,我没有得到任何回应并最终永远等待。稍后我也会实现异步套接字,但我想先做一个简单的例子。
【解决方案2】:

我想你可能有文件结尾左右。你能不能在读取流之前设置流位置

stream.position = 0;

http://msdn.microsoft.com/en-us/library/vstudio/system.io.stream.read

【讨论】:

    【解决方案3】:

    Encoding.UTF8.GetString 不应用于任意字节数组。 例如:压缩后的字节可能包含 NULL 字符,这在 UTF-8 编码的文本中是不允许的,除非用作终止符。

    如果您想打印接收到的字节以进行调试,也许您应该将它们打印为整数。

    【讨论】:

    • 代码没有那么远。问题出在上一行。正如我也说过的,如果数据没有被压缩,代码就可以正常工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-12
    • 1970-01-01
    • 1970-01-01
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多