簡體   English   中英

為什么我的DeflateStream無法通過TCP正確接收數據?

[英]Why is my DeflateStream not receiving data correctly over TCP?

我在本地計算機上的客戶端和服務器設置上有一個TcpClient類。 我一直在使用網絡流來促進2成功之間的來回通信。

繼續前進我試圖在通信中實現壓縮。 我嘗試過GZipStream和DeflateStream。 我決定專注於DeflateStream。 但是,連接暫停而不讀取數據。

我嘗試了4種不同的實現,由於服務器端沒有讀取傳入數據和連接超時,所有實現都失敗了。 我將重點介紹我最近嘗試過的兩個實現,據我所知應該可行。

客戶端分解為此請求:有兩個單獨的實現,一個沒有編寫器。

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;
    }
}

EDIT NetworkStream有一個底層的Socket屬性,它有一個Available屬性。 MSDN說這是關於可用的屬性。

獲取從網絡接收並可以讀取的數據量。

在下面的調用之前可用的是77.讀取1個字節后,該值為0。

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

似乎沒有關於DeflateStream消耗整個底層流的任何文檔,我不知道為什么當有明確的調用來讀取特定的字節數時它會做這樣的事情。

有誰知道為什么會發生這種情況,或者是否有辦法保留未來讀取的基礎數據? 基於這個'特性'和之前發表的文章說明DeflateStream必須關閉才能完成發送(刷新不起作用),似乎DeflateStreams在網絡使用方面可能受到限制,特別是如果有人希望通過測試來對抗DOS攻擊接受完整流之前的傳入數據。

我能想到看你的代碼的基本缺陷是可能誤解網絡流和壓縮的工作原理。

如果您繼續使用一個DeflateStream,我認為您的代碼可能會有效。 但是,您在快速閱讀中使用一個,然后再創建另一個。

我將試着用一個例子來解釋我的推理。 假設您有8個字節的原始數據以壓縮方式通過網絡發送。 現在讓我們假設一個參數,原始數據的每個字節(8位)將以壓縮形式壓縮為6位。 現在讓我們看看你的代碼對此做了什么。

從網絡流中,您讀取的內容不會少於1個字節。 你不能只拿1位。 您需要1個字節,2個字節或任意數量的字節,但不能使用位。

但是,如果您只想接收原始數據的1個字節,則需要讀取壓縮數據的第一個完整字節。 但是,只有6位壓縮數據代表未壓縮數據的第一個字節。 第一個字節的最后2位用於原始數據的第二個字節。

現在,如果你在那里剪切流,剩下的就是網絡流中的5個字節,這些字節沒有任何意義,也無法解壓縮。

deflate算法比這更復雜,因此如果它不允許你在某一點停止從NetworkStream讀取並從中間繼續使用新的DeflateStream,那么它就非常有意義。 為了將數據解壓縮為原始形式,必須存在解壓縮的上下文。 一旦你在快速閱讀中處理了第一個DeflateStream,這個上下文就消失了,你無法繼續。

因此,要解決您的問題,請嘗試僅創建一個DeflateStream並將其傳遞給您的函數,然后進行處理。

這在許多方面都被打破了。

  1. 您假設讀取調用將讀取您想要的確切字節數。 它可能會讀取一個字節塊中的所有內容。
  2. DeflateStream有一個內部緩沖區。 它不能是任何其他方式:輸入字節與輸出字節不對應1:1。 必須有一些內部緩沖。 必須使用一個這樣的流。
  3. 與UTF-8相同的問題:UTF-8編碼的字符串不能在字節邊界處拆分。 有時,您的Unicode數據會出現亂碼。
  4. 不要觸摸ReceiveBufferSize,它沒有任何幫助。
  5. 我認為你無法可靠地刷新deflate流,因為輸出可能位於部分字節位置。 您可能應該設計一種消息成幀格式,其中您將壓縮長度作為未壓縮整數前置。 然后,在長度之后發送壓縮的放氣流。 這是可靠的可解碼方式。

解決這些問題並不容易。

由於您似乎控制客戶端和服務器,因此您應該丟棄所有這些,而不是設計自己的網絡協議。 使用更高級別的機制,例如Web服務,HTTP,protobuf。 一切都比你在那里更好。

基本上我上面發布的代碼有一些問題。 首先,當我讀取數據時,我沒有做任何事情以確保數據全部被讀入。根據microsoft文檔

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);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM