.Net 下通過快取提高TCP傳輸速度
.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();
- 接收過程
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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- TCP/IP 通訊傳輸流TCP
- 通過PowerShell獲取TCP響應(類Telnet)TCP
- liunx通過TCP傳送資訊TCP
- 通過PowerShell傳送TCP請求TCP
- java tcp網路通訊 傳輸檔案JavaTCP
- 怎麼提高go讀取標準輸入的速度Go
- 機器學習演算法讓TCP傳輸速度提升一倍機器學習演算法TCP
- ASP.NET Web API通過ActionFilter來實現快取ASP.NETWebAPIFilter快取
- ASP.NET Web API中通過ETag實現快取ASP.NETWebAPI快取
- 蘋果電腦MacBook Pro再曝光:傳輸速度極快!蘋果Mac
- 通過RMAN-transport獲取傳輸表空間檔案
- Java記憶體快取-通過Google Guava建立快取Java記憶體快取GoGuava
- Win10系統怎麼通過修改執行緒數提高OneDrive上傳速度Win10執行緒
- asp.net輸出快取的使用ASP.NET快取
- 希捷推出新行動硬碟 容量超大傳輸速度快希捷硬碟
- TCP可靠傳輸原理TCP
- 通過HTTP Header控制快取HTTPHeader快取
- Java記憶體快取-通過Map定製簡單快取Java記憶體快取
- 理解TCP/IP、UDP - 通過nodejs的net模組TCPUDPNodeJS
- 在asp.net web api中利用過濾器設定輸出快取ASP.NETWebAPI過濾器快取
- .NET 快取快取
- linux下遠端傳送檔案命令,通過ssh協議傳輸檔案Linux協議
- TCP傳輸協議詳解TCP協議
- TCP傳輸資料長度TCP
- ASP.NET Core - 快取之記憶體快取(下)ASP.NET快取記憶體
- 無線通訊模組透過TCP/IP協議實現與PC端的資料傳輸TCP協議
- 簡單實現TCP下的大檔案高效傳輸TCP
- PHP 輸出快取PHP快取
- TCP/IP五層模型-傳輸層-TCP協議TCP模型協議
- ASP.NET 2.0中的頁面輸出快取ASP.NET快取
- java tcp 網路通訊--使用多執行緒傳輸檔案JavaTCP執行緒
- 擁抱.NET Core系列:MemoryCache 快取過期快取
- [譯]通過超市買牛奶來學習快取快取
- dotnet 快取快取
- 傳輸層協議 TCP 和 UDP協議TCPUDP
- TCP/IP傳輸層,你懂多少?TCP
- 【Netty】Netty傳輸Netty
- Python 基於 TCP 傳輸協議的網路通訊實現PythonTCP協議