为什么我的DeflateStream无法通过TCP正确接收数据?

时间:2016-02-17 15:07:23

标签: c# tcpclient deflatestream

我在本地计算机上的客户端和服务器设置上有一个TcpClient类。我一直在使用网络流来促进2成功之间的来回通信。

继续前进我正试图在通信中实现压缩。我尝试过GZipStream和DeflateStream。我决定专注于DeflateStream。但是,连接暂停而不读取数据。

由于服务器端未读取传入数据和连接超时,我尝试了4种不同的实现。我将重点介绍我最近尝试过的两个实现,据我所知应该可以工作。

客户端细分为此请求:有两个单独的实现,一个没有使用streamwriter。

textToSend = ENQUIRY + START_OF_TEXT + textToSend + END_OF_TEXT;

// Send XML Request
byte[] request = Encoding.UTF8.GetBytes(textToSend);

using (DeflateStream streamOut = new DeflateStream(netStream, CompressionMode.Compress, true))
{
    //using (StreamWriter sw = new StreamWriter(streamOut))
    //{
    //    sw.Write(textToSend);
    //    sw.Flush();
    streamOut.Write(request, 0, request.Length);
    streamOut.Flush();
    //}
}

服务器收到请求,我做了 1.)如果第一个字符符合我的预期,请快速阅读第一个字符 2.)我继续阅读其余的内容。

第一次读取正常工作,如果我想读取整个流,它就在那里。但是我只想读取第一个字符并对其进行评估,然后继续使用我的LongReadStream方法。

当我尝试继续读取流时,没有要读取的数据。我猜测数据在第一次读取时丢失了,但我不确定如何确定。当我使用普通的NetworkStream时,所有这些代码都能正常工作。

这是服务器端代码。

private void ProcessRequests()
{
    //  This method reads the first byte of data correctly and if I want to
    // I can read the entire request here.  However, I want to leave
    // all that data until I want it below in my LongReadStream method.
    if (QuickReadStream(_netStream, receiveBuffer, 1) != ENQUIRY)
    {
        // Invalid Request, close connection
        clientIsFinished = true;
        _client.Client.Disconnect(true);
        _client.Close();
        return;
    }



    while (!clientIsFinished)  // Keep reading text until client sends END_TRANSMISSION
    {
        // Inside this method there is no data and the connection times out waiting for data
        receiveText = LongReadStream(_netStream, _client);

        // Continue talking with Client...
    }
    _client.Client.Shutdown(SocketShutdown.Both);
    _client.Client.Disconnect(true);
    _client.Close();
}


private string LongReadStream(NetworkStream stream, TcpClient c)
{
    bool foundEOT = false;
    StringBuilder sbFullText = new StringBuilder();
    int readLength, totalBytesRead = 0;
    string currentReadText;
    c.ReceiveBufferSize = DEFAULT_BUFFERSIZE * 100;

    byte[] bigReadBuffer = new byte[c.ReceiveBufferSize];

    while (!foundEOT)
    {
        using (var decompressStream = new DeflateStream(stream, CompressionMode.Decompress, true))
        {
            //using (StreamReader sr = new StreamReader(decompressStream))
            //{
                //currentReadText = sr.ReadToEnd();
            //}
            readLength = decompressStream.Read(bigReadBuffer, 0, c.ReceiveBufferSize);
            currentReadText = Encoding.UTF8.GetString(bigReadBuffer, 0, readLength);
            totalBytesRead += readLength;
        }

        sbFullText.Append(currentReadText);

        if (currentReadText.EndsWith(END_OF_TEXT))
        {
            foundEOT = true;
            sbFullText.Length = sbFullText.Length - 1;
        }
        else
        {
            sbFullText.Append(currentReadText);
        }

        // Validate data code removed for simplicity


    }
    c.ReceiveBufferSize = DEFAULT_BUFFERSIZE;
    c.ReceiveTimeout = timeOutMilliseconds;
    return sbFullText.ToString();

}



private string QuickReadStream(NetworkStream stream, byte[] receiveBuffer, int receiveBufferSize)
{
    using (DeflateStream zippy = new DeflateStream(stream, CompressionMode.Decompress, true))
    {
        int bytesIn = zippy.Read(receiveBuffer, 0, receiveBufferSize);
        var returnValue = Encoding.UTF8.GetString(receiveBuffer, 0, bytesIn);
        return returnValue;
    }
}

修改 NetworkStream有一个底层的Socket属性,它有一个Available属性。 MSDN对可用的财产说了这个。

  

获取从网络接收的数据量   可以阅读。

在下面的调用之前可用的是77.读取1个字节后,该值为0.

//receiveBufferSize = 1
int bytesIn = zippy.Read(receiveBuffer, 0, receiveBufferSize);

似乎没有关于DeflateStream消耗整个底层流的任何文档,我不知道为什么当有明确的调用来读取特定的字节数时它会做这样的事情

有谁知道为什么会发生这种情况,或者是否有办法保留未来读取的基础数据?基于此功能'并且必须关闭以前article that I read说明DeflateStream才能完成发送(冲洗赢了工作)似乎DeflateStreams在网络使用方面可能受到限制,特别是如果有人希望通过测试传入数据来对抗DOS攻击接受完整的流。

3 个答案:

答案 0 :(得分:2)

我可以考虑查看代码的基本缺陷是可能误解网络流和压缩的工作原理。

如果您继续使用一个DeflateStream,我认为您的代码可能会有效。但是,您在快速阅读中使用一个,然后再创建另一个。

我会试着用一个例子来解释我的推理。假设您有8个字节的原始数据以压缩方式通过网络发送。现在让我们假设一个参数,原始数据的每个字节(8位)将以压缩形式压缩为6位。现在让我们看看你的代码对此做了什么。

从网络流中,您读取的内容不会少于1个字节。你不能只拿1位。您需要1个字节,2个字节或任意数量的字节,但不能使用位。

但是如果您只想接收原始数据的1个字节,则需要读取压缩数据的第一个完整字节。但是,只有6位压缩数据代表未压缩数据的第一个字节。第一个字节的最后2位用于原始数据的第二个字节。

现在,如果您在那里剪切流,剩下的就是网络流中的5个字节,它们没有任何意义,也无法解压缩。

deflate算法比这更复杂,因此如果它不允许你在某一点停止从NetworkStream读取并从中间继续使用新的DeflateStream,那么它就非常有意义。为了将数据解压缩为原始形式,必须存在解压缩的上下文。一旦你在快速阅读中处理了第一个DeflateStream,这个上下文就消失了,你无法继续。

因此,要解决您的问题,请尝试仅创建一个DeflateStream并将其传递给您的函数,然后将其丢弃。

答案 1 :(得分:2)

这在许多方面都被打破了。

  1. 您假设读取调用将读取您想要的确切字节数。它可能会读取一个字节块中的所有内容。
  2. DeflateStream有一个内部缓冲区。它不能是任何其他方式:输入字节与输出字节不对应1:1。必须有一些内部缓冲。您必须使用一个此类流。
  3. 与UTF-8相同的问题:UTF-8编码的字符串不能在字节边界处拆分。有时,您的Unicode数据会出现乱码。
  4. 请勿触摸ReceiveBufferSize,它无论如何都无济于事。
  5. 我认为你无法可靠地刷新deflate流,因为输出可能位于部分字节位置。您可能应该设计一种消息成帧格式,其中您将压缩长度作为未压缩整数前置。然后,在长度之后发送压缩的放气流。这可以通过可靠的方式解码。
  6. 解决这些问题并不容易。

    由于您似乎控制客户端和服务器,因此您应该丢弃所有这些,而不是设计自己的网络协议。使用更高级别的机制,例如Web服务,HTTP,protobuf。一切都比你那里的要好。

答案 2 :(得分:0)

基本上我上面发布的代码有些问题。首先,当我读取数据时,我没有做任何事情来确保数据全部被读入。根据微软文档

  

Read操作读取尽可能多的数据,直到   size参数指定的字节数。

在我的情况下,我没有确保我的读取能够获得我预期的所有数据。

这可以通过此代码完成。

byte[] data= new byte[packageSize];
    bytesRead = _netStream.Read(data, 0, packageSize);
    while (bytesRead < packageSize)
        bytesRead += _netStream.Read(data, bytesRead, packageSize - bytesRead);

除了这个问题,我还有一个使用DeflateStream的根本问题 - 即我不应该使用DeflateStream来写入底层的NetworkStream。正确的方法是首先使用DeflateStream将数据压缩为ByteArray,然后直接使用NetworkStream发送ByteArray。

使用此方法有助于通过网络正确压缩数据,并且属性读取另一端的数据。

您可能会指出我必须知道数据的大小,这是事实。每个电话都有一个8字节的标题&#39;包括压缩数据的大小和未压缩时的数据大小。虽然我认为第二个是非常不需要的。

这里的代码就在这里。请注意,变量compressedSize有两个目的。

 int packageSize = streamIn.Read(sizeOfDataInBytes, 0, 4);
 while (packageSize!= 4)
 {
    packageSize+= streamIn.Read(sizeOfDataInBytes, packageSize, 4 - packageSize);
 }
 packageSize= BitConverter.ToInt32(sizeOfDataInBytes, 0);

通过这些信息,我可以正确使用我首先向您展示的代码来完全获取内容。

一旦我有了完整的压缩字节数组,我就可以得到传入的数据:

var output = new MemoryStream();
using (var stream = new MemoryStream(bufferIn))
{
    using (var decompress = new DeflateStream(stream, CompressionMode.Decompress))
    {
        decompress.CopyTo(output);;
    }
}
output.Position = 0;
var unCompressedArray = output.ToArray();
output.Close();
output.Dispose();
return Encoding.UTF8.GetString(unCompressedArray);