.Net 下通過快取提高TCP傳輸速度

iDotNetSpace發表於2009-11-05

 .Net 提供了一個NetworkStream 用於TCP 的讀寫,實際使用時發現直接操作效率很低,哪怕把TCP 的傳送快取和接受快取設定很大也沒有太大提高。後來在對 NetworkStream 讀寫前設定了快取,效能一下子提高了很多。

    從實際測試結果看設定自己的寫快取,對效能的提升最為顯著。我分析了一下,其原因很可能是在向NetworkStream 序列化物件時,序列化程式呼叫了大量的Write 方法向NetworkStream寫入資料,每次向NetworkStream寫入資料,資料被首先寫入TCP的傳送快取,並且由呼叫執行緒設定一個訊號通知.Net framework 內部的TCP執行緒傳送緩衝區中已經有資料,TCP執行緒被啟用並讀取傳送緩衝區中的資料,組包並向網路卡寫入資料。頻繁的呼叫 NetworkStream.Write 寫入小塊資料將導致呼叫執行緒和TCP執行緒反覆切換,並大量觸發網路卡中斷,導致傳送效率低下。如果我們在傳送前將資料快取並按較大的資料塊傳送給TCP執行緒,則大大減少執行緒切換和網路卡中斷數量,從而大大提高傳輸效率。

    問題到這裡還沒有結束,我們傳送的物件往往較大,如果我們將傳送物件全部序列化到buffer中再傳送,那麼勢必佔用大量記憶體,實際上我們無法忍受這種對記憶體無限制申請的行為,試想一個1G大小的物件,我們在傳送前為它另外再開闢1個G的記憶體來快取,對於系統來說簡直是無法忍受。由於我們用.net 傳送資料,我們在傳送時需要將物件序列化到流中,而不能像 C/C++那樣直接通過指標來讀取資料(當然你也可以用unsafe程式碼,但這種方式會帶來其他問題,而且並不為大家所推薦),所以我們需要開發一個專門用 TCP 傳送快取的流來處理讀寫前的快取。為此我開發了一個 TcpCacheStream 類,這個類被用在讀寫 NetworkStream 前先進行快取。

    呼叫方法很簡單

   

  • 傳送過程
object msg;
//初始化 msg 過程省略
System.Net.Sockets.NetworkStream _ClientStream;
//初始化 _ClientStream 過程省略
 
//建立TcpCacheStream 
TcpCacheStream tcpStream = new TcpCacheStream(_ClientStream);
 
//二進位制序列化 msg 物件到 TcpCacheStream 
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(tcpStream, msg);
 
//將快取中最後一包的資料傳送出去
tcpStream.Flush();
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   
  • 接收過程
 
System.Net.Sockets.NetworkStream _ClientStream;
//初始化 _ClientStream 過程省略
 
//建立TcpCacheStream 
TcpCacheStream tcpStream = new TcpCacheStream(_ClientStream);
 
//從 TcpCacheStream 二進位制反序列化
IFormatter formatter = new BinaryFormatter();
objcet result = formatter.Deserialize(tcpStream);

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

TcpCacheStream 類為呼叫者封裝了快取的過程,這個快取過程實際並不複雜,傳送時資料先寫入TcpCacheStream的buf中,當buf滿後才向NetworkStream 寫入資料,否則只快取。由於最後一包不能保證正好填滿buf,我們在寫入資料後一定要呼叫 Flush 方法,將所有資料都傳送出去。接收的過程反過來,如果buf中沒有資料,就先將資料讀入到buf中,然後再COPY給呼叫者,如果已經有資料則直接COPY給呼叫者。

 

TcpCacheStream 的程式碼如下:

    [Serializable]
    public class TcpCacheStream : Stream
    {
        #region Private fields
        const int BUF_SIZE = 4096;
        private byte[] _Buf = new byte[BUF_SIZE];
 
        private MemoryStream _CacheStream = new MemoryStream(BUF_SIZE);
        private NetworkStream _NetworkStream;
 
        private int _BufLen = 0;
 
        #endregion
 
        #region Private properties
 
        private MemoryStream CacheStream
        {
            get
            {
                return _CacheStream;
            }
        }
 
        #endregion
 
 
        #region Public properties
 
        /// 
        /// get or set the Network Stream
        /// 
        public NetworkStream NetworkStream
        {
            get
            {
                return _NetworkStream;
            }
        }
 
        #endregion
 
 
        public TcpCacheStream(NetworkStream networkStream)
        {
            _NetworkStream = networkStream;
        }
 
        #region Implement stream class
 
        public override bool CanRead
        {
            get
            {
                return true;
            }
        }
 
        public override bool CanSeek
        {
            get
            {
                return false;
            }
        }
 
        public override bool CanWrite
        {
            get
            {
                return true;
            }
        }
 
        public override void Flush()
        {
            NetworkStream.Write(_Buf, 0, _BufLen);
            NetworkStream.Flush();
        }
 
        public override long Length
        {
            get
            {
                throw new Exception("This stream can not seek!");
            }
        }
 
        public override long Position
        {
            get
            {
                throw new Exception("This stream can not seek!");
            }
 
            set
            {
                throw new Exception("This stream can not seek!");
            }
        }
 
        public override int Read(byte[] buffer, int offset, int count)
        {
            int len = 0;
 
            //If cache is not empty, read from cache
            if (CacheStream.Length > CacheStream.Position)
            {
                len = CacheStream.Read(buffer, offset, count);
                return len;
            }
 
            //Fill cache
            len = NetworkStream.Read(_Buf, 0, BUF_SIZE);
 
            if (len == 0)
            {
                return 0;
            }
 
            CacheStream.Position = 0;
            CacheStream.Write(_Buf, 0, len);
            CacheStream.SetLength(len);
            CacheStream.Position = 0;
 
            len = CacheStream.Read(buffer, offset, count);
 
            return len;
        }
 
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new Exception("This stream can not seek!");
        }
 
        public override void SetLength(long value)
        {
            throw new Exception("This stream can not seek!");
        }
 
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (offset + count > buffer.Length)
            {
                throw new ArgumentException("Count + offset large then buffer.Length");
            }
 
            int remain = count - (BUF_SIZE - _BufLen);
 
            if (remain < 0)
            {
                Array.Copy(buffer, offset, _Buf, _BufLen, count);
                _BufLen = BUF_SIZE + remain;
            }
            else
            {
                Array.Copy(buffer, offset, _Buf, _BufLen, BUF_SIZE - _BufLen);
                NetworkStream.Write(_Buf, 0, _Buf.Length);
 
                Array.Copy(buffer, offset + BUF_SIZE - _BufLen, _Buf, 0, remain);
 
                _BufLen = remain;
            }
        }
 
        #endregion
    }

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-618178/,如需轉載,請註明出處,否則將追究法律責任。

相關文章