[.Net] 資料接收的常識

在 .Net 中,基礎通訊主要就是 SerialPort 跟 Socket ,其中 Socket 之下,又有延伸兩個類別,分別是 TcpClient 跟 TcpListener 。

Socket 常用的是 TCPIP/UDP ,常用通訊協定可查詢 RFC 相關文件,SerialPort 視硬體而定,常用 RS-232/RS-422/RS-485 ,多數為自訂通訊協定,工業常用 Modbus RTU/ASCII。

不論用哪一種,通訊架構都是跟下圖相同:

通訊示意圖

通訊示意圖

首先,傳輸過程中,作業系統會提供一個緩衝區給軟體應用,不管是使用 SerialPort 或是 Socket ,我們就簡稱這端為 Client 端。

這個緩衝區會依預設值配置,但是開發人員是可以透過程式碼變更大小,預設 SerialPort 的傳送緩衝區為 2kb ,接收緩衝區最小為 4kb ,預設 Socket 的傳誦與接收緩衝區均為 8kb 。

電腦從接受程式碼寫出或讀入時,都是將資料送到緩衝區,這個速度非常的快,目前電腦速度都是在 1ms 以內完成,不可量測,但可以概估,如網卡跟序列埠都在南橋上,以現在系統匯流排破 Gb 的速度算,8kb 存取速度也只是十萬分之一秒。

但問題是從緩衝區到裝置或是 Server ,速度都很慢,比如說 SerialPort 用個 9600 ,ADSL 是 2M/64k 之類的。

資料傳送, .Net 已經幫你包好,即使你的資料大於緩衝區,他都會開一條執行緒幫你慢慢傳,所以對於大多數人來說,資料送出就等同結束,就以為已經傳輸完畢。

但是資料接收不是,因為接收資料的時間,常常遠比想像中要長,所以不要妄想用 Read 方法去讀資料時,所有資料都傳到接收緩衝區了。

舉個例子好了,選舉到了,總會接到拜票電話吧?拜票電話是用電腦送出語音,所以就是講話的人,完全不管你的回應,一直講,講完就結束了,好比傳送的那條路徑。但是你是接電話的人,從你接到電話開始,到你了解意圖知道是哪位候選人、要不要掛電話、要不要聽完,你腦袋不曉得轉幾次了,但你還是不知道電話會說到何時,就跟程式處理一樣,不要幻想從緩衝區讀取資料時,所有資料都接收到可以一次讀取了,好比接收那條路徑。

因此接收資料需要用迴圈不斷的讀取,直到讀到預期長度或是結束標記視為正常結束,若是訊號本身是沒有特定終止符號的,就是無訊號到逾時為止。

另外在 Socket 通訊上,很多人喜歡用 DataReceived 事件,但這個事件要小心用,不會用乾脆不要用。

.Net 處理 SerialPort 通訊接收事件是這樣做:
1. 預設接收到一個 Bytes 資料就會觸發事件 (SerialPort.ReceivedBytesThreshold 屬性可調整 Bytes 數)
2. 系統通知程式有事件 (視系統忙碌程度,此時可能會多接到幾個 Bytes)
3. .Net 程式收到通知後,開個新的執行緒執行 DataReceived 事件。
4. 主執行緒繼續執行。

這裡面在 3 的時候,若程式沒處理好,比如說在裡面放個執行時間很長的程式碼,當 1 再度觸發時,就會再創建一條執行緒執行 3 ,若前後兩個執行緒同時對緩衝區做 Read 動作,這時會造成 SerialPort 鎖死,之後不斷的訊號接收,執行緒就會越來越多,大概執行緒累積接近到 512 條時,整個程式會停在那。

我個人偏好是直接開新的執行緒單獨接收資料,不要使用事件來處理。

這時就有新的問題,如何暫存接收中的資料?

以目前記憶體來說,隨便開個 10 MB 的陣列不過分吧?當然你可以選擇小一點,就以 10 MB 為範例,10 MB 以內,我推薦用 MemoryStream 接收,超過的話,通常是傳輸檔案之類的,直接用 FileStream 接收,這樣可以降低複雜度,並提升速度,當然如果是大檔案,比較完整的作法是先用 MemoryStream 接收,累積到 64kb 寫入一次 FileStream ,重置 MemoryStream ,可降低磁碟存取,提升速度。

宣告 Stream
迴圈開始
 Thread.Sleep(1)
 宣告 緩衝Byte陣列(接收緩衝區大小)
 緩衝區內位元組數 = 物件.Read(緩衝Byte陣列, 0, 接收緩衝區大小)
 Stream.Write(緩衝Byte陣列, 0, 接收緩衝區大小)
 檢查離開條件
迴圈結束

其中,Thread.Sleep(1) 是考慮釋出 CPU 資源的最小建議為 1ms ,如果用工作緒處理,也可以睡到 100ms。

如果 Stream 是使用 MemoryStream 時,當需要位元組陣列時,可以用
完整Byte陣列 = MemoryStream.ToArray()
即可

大體上這樣就算是一個完整的流程。

如果是網路傳輸檔案,我個人建議是先把傳送緩衝區與接收緩衝區都調大。
比如說是內網吧,網路 1GHz ,預設緩衝區大小為 8092 ,所以
16384 = 1GHz/8/8092
也就是說,一秒內,可以填滿緩衝區 16384 次,則每次迴圈需在萬分之一秒內處理完畢,才不會造成傳輸速度變慢或是緩衝區爆掉。

假設程式處理速度每個迴圈或事件為 15ms ,則接收緩衝區應放大到 512kb 左右,當然緩衝區放大,程式實體使用的記憶體也會增加,如果 Socket 會開很多,就要自己取捨,減少緩衝區大小,避免造成作業系統負擔。

Categories: 技術分享 | Tags: , , , , , | 1 則迴響

文章導覽

One thought on “[.Net] 資料接收的常識

  1. 延伸閱讀:關於 Sleep 的部分,建議看一下這篇跟回應內的相關連結。
    https://tlcheng.wordpress.com/2007/03/07/vb6vbnet-cpu-%e8%b3%87%e6%ba%90%e9%87%8b%e5%87%ba/

    喜歡

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 變更 )

Twitter picture

You are commenting using your Twitter account. Log Out / 變更 )

Facebook照片

You are commenting using your Facebook account. Log Out / 變更 )

Google+ photo

You are commenting using your Google+ account. Log Out / 變更 )

連結到 %s

在WordPress.com寫網誌.

%d 位部落客按了讚: