6

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

展望未来,我正在尝试在通信中实现压缩。我试过 GZipStream 和 DeflateStream。我决定专注于 DeflateStream。但是,连接挂起,现在没有读取数据。

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

客户端被分解为这个请求:有 2 个单独的实现,一个有流写器,一个没有。

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 属性,它有一个可用的属性。MSDN 对可用属性进行了说明。

获取已从网络接收并可供读取的数据量。

在调用之前可用是 77。读取 1 个字节后,值是 0。

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

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

有谁知道为什么会发生这种情况,或者是否有办法保留基础数据以供将来阅读?基于这个“功能”和我读过的一篇文章,说明必须关闭 DeflateStream 才能完成发送(刷新不起作用),似乎 DeflateStreams 在网络使用方面可能受到限制,特别是如果希望通过测试来对抗 DOS 攻击接受完整流之前的传入数据。

4

3 回答 3

3

我可以想到查看您的代码的基本缺陷是对网络流和压缩如何工作的可能误解。

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

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

从网络流中,您不能读取少于 1 个字节。你不能只取 1 位。您需要 1 个字节、2 个字节或任意数量的字节,但不是位。

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

现在,如果您在此处剪切流,则网络流中剩下的 5 个字节没有任何意义且无法解压缩。

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

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

于 2016-02-17T16:25:30.970 回答
3

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

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

解决这些问题并不容易。

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

于 2016-02-17T16:51:23.840 回答
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 字节的“标头”,其中包括压缩数据的大小和未压缩时的数据大小。虽然我认为第二个是完全不需要的。

代码在这里。请注意,变量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);
于 2016-02-26T18:28:02.280 回答