C# Socket通信三大問題詳解
C# Socket通信三大問題是什么呢?讓我們開始講述:
C# Socket通信三大問題之數據包界限符問題。
根據原項目中交通部標準,在連續觀測站中數據包中,使用﹤﹥兩個字符表示有效數據包開始和結束。實際項目有各自的具體技術規范
C# Socket通信三大問題之數據包不連續問題。
在TCP/IP等通信中,由于時延等原因,一個數據包被Socket做兩次或多次接收,此時在接收第一個包后,必須保存到TSession的DatagramBuffer中,在以后一并處理
C# Socket通信三大問題包并發與重疊問題。
由于客戶端發送過快或設備故障等原因,一次接收到一個半、兩個或多個包文。此時,也需要處理、一個半、兩個或多個包
先補充異步BeginReceive()回調函數EndReceiveData()中的數據包分合函數ResolveBuffer()。
下面是C# Socket通信三大問題的實例演示:
- /// ﹤summary﹥
- /// 1) 報文界限字符為﹤﹥,其它為合法字符,
- /// 2) 按報文頭、界限標志抽取報文,可能合并包文
- /// 3) 如果一次收完數據,此時 DatagramBuffer 為空
- /// 4) 否則轉存到包文緩沖區 session.DatagramBuffer
- /// ﹤/summary﹥
- private void ResolveBuffer(TSession session, int receivedSize)
- {
- // 上次留下的報文緩沖區非空(注意:必然含有開始字符 ﹤,空時不含 ﹤)
- bool hasBeginChar = (session.DatagramBufferLength ﹥ 0);
- int packPos = 0; // ReceiveBuffer 緩沖區中包的開始位置
- int packLen = 0; // 已經解析的接收緩沖區大小
- byte dataByte = 0; // 緩沖區字節
- int subIndex = 0; // 緩沖區下標
- while (subIndex ﹤ receivedSize)
- {
- // 接收緩沖區數據,要與報文緩沖區 session.DatagramBuffer 同時考慮
- dataByte = session.ReceiveBuffer[subIndex];
- if (dataByte == TDatagram.BeginChar) // 是數據包的開始字符﹤,則前面的包文均要放棄
- {
- // ﹤前面有非空串(包括報文緩沖區),則前面是錯包文,防止 AAA﹤A,1,A﹥ 兩個報文一次讀現象
- if (packLen ﹥ 0)
- {
- Interlocked.Increment(ref _datagramCount); // 前面有非空字符
- Interlocked.Increment(ref _errorDatagramCount); // 一個錯誤包
- this.OnDatagramError();
- }
- session.ClearDatagramBuffer(); // 清空會話緩沖區,開始一個新包
- packPos = subIndex; // 新包起點,即﹤所在位置
- packLen = 1;// 新包的長度(即﹤)
- hasBeginChar = true; // 新包有開始字符
- }
- else if (dataByte == TDatagram.EndChar) // 數據包的結束字符 ﹥
- {
- if (hasBeginChar) // 兩個緩沖區中有開始字符﹤
- {
- ++packLen; // 長度包括結束字符﹥
- // ﹥前面的為正確格式的包,則分析該包,并準備加入包隊列
- AnalyzeOneDatagram(session, packPos, packLen);
- packPos = subIndex + 1; // 新包起點。注意:subIndex 在循環最后處 + 1
- packLen = 0; // 新包長度
- }
- else // ﹥前面沒有開始字符,則認為結束字符﹥為一般字符,待后續的錯誤包處理
- {
- ++packLen; // hasBeginChar = false;
- }
- }
- else // 非界限字符﹤﹥,就是是一般字符,長度 + 1,待解析包處理
- {
- ++packLen;
- }
- ++subIndex; // 增加下標號
- } // end while
- if (packLen ﹥ 0) // 剩下的待處理串,分兩種情況
- {
- // 剩下包文,已經包含首字符且不超長,轉存到包文緩沖區中,待下次處理
- if (hasBeginChar && packLen +
- session.DatagramBufferLength ﹤= _maxDatagramSize)
- {
- session.CopyToDatagramBuffer(packPos, packLen);
- }
- else // 不含首字符,或超長
- {
- Interlocked.Increment(ref _datagramCount);
- Interlocked.Increment(ref _errorDatagramCount);
- this.OnDatagramError();
- session.ClearDatagramBuffer(); // 丟棄全部數據
- }
- }
- }
C# Socket通信三大問題之分析包文AnalyzeOneDatagram()函數代碼補充如下:
- /// ﹤summary﹥
- /// 具有﹤﹥格式的數據包加入到隊列中
- /// ﹤/summary﹥
- private void AnalyzeOneDatagram(
- TSession session, int packPos, int packLen)
- {
- if (packLen + session.DatagramBufferLength ﹥ _maxDatagramSize)
- // 超過長度限制
- {
- Interlocked.Increment(ref _datagramCount);
- Interlocked.Increment(ref _errorDatagramCount);
- this.OnDatagramError();
- }
- else // 一個首尾字符相符的包,此時需要判斷其類型
- {
- Interlocked.Increment(ref _datagramCount);
- TDatagram datagram = new TDatagram();
- if (!datagram.CheckDatagramKind())
- // 包格式錯誤(只能是短期BG、或長期SG包)
- {
- Interlocked.Increment(ref _datagramCount);
- Interlocked.Increment(ref _errorDatagramCount);
- this.OnDatagramError();
- datagram = null; // 丟棄當前包
- }
- else // 實時包、定期包,先解析數據,判斷正誤,并發回確認包
- {
- datagram.ResolveDatagram();
- if (true) // 正確的包才入包隊列
- {
- Interlocked.Increment(ref _datagramQueueCount);
- lock (_datagramQueue)
- {
- _datagramQueue.Enqueue(datagram); // 數據包入隊列
- }
- }
- else
- {
- Interlocked.Increment(ref _errorDatagramCount);
- this.OnDatagramError();
- }
- }
- }
- session.ClearDatagramBuffer(); // 清包文緩沖區
- }
C# Socket通信三大問題之TSession的拷貝轉存數據包文的方法CopyToDatagramBuffer()代碼如下:
- /// ﹤summary﹥
- /// 拷貝接收緩沖區的數據到數據緩沖區(即多次讀一個包文)
- /// ﹤/summary﹥
- public void CopyToDatagramBuffer(int startPos, int packLen)
- {
- int datagramLen = 0;
- if (DatagramBuffer != null) datagramLen =
- DatagramBuffer.Length;
- // 調整長度(DataBuffer 為 null 不會出錯)
- Array.Resize(ref DatagramBuffer,
- datagramLen + packLen);
- // 拷貝到數據就緩沖區
- Array.Copy(ReceiveBuffer, startPos,
- DatagramBuffer, datagramLen, packLen);
- }
代碼中注釋比較詳細了,下面指出C# Socket通信三大問題實例開發思路:
使用TSession會話對象的字節數組ReceiveBuffer保存BeginReceiver()接收到的數據,使用字節數組DatagramBuffer保存一次接收后分解或合并的剩下的包文。本項目中,由于是5分鐘一個包,正常情況下不需要用到DatagramBuffer數組
處理ReceiveBuffer中的字節數據包時,先考慮DatagramBuffer是否有開始字符﹤。如果有,則當前包文是前個包文的補充,否則前個包文是錯誤的。正確的包文可能存在于兩個緩沖區中,見分析函數AnalyzeOneDatagram()
分析完接收數據包后,剩下的轉存到DatagramBuffer中,見函數CopyToDatagramBuffer()
設計時考慮的另一個重要問題就是處理速度。如果自動觀測站達到100個,此時5*60=300秒鐘就有100個包,即每3秒種一個包,不存在處理速度慢問題。但是,真正耗時的是判斷包是否重復!特別地,當設備故障時存在混亂上傳數據包現象,此時將存在大量的重復包。筆者采用了所謂的區間判重算法,較好地解決了判重速度問題,使得系統具有很好的可伸縮性(分析算法的論文被EI核心版收錄,呵呵,意外收獲)。事實上,前年的交通部接收服務器還不具備該項功能,可能是太費時間了。
還有,就是在.NET Framework的托管CLR下,系統本身的響應速度如何?當時的確沒有把握,認為只要穩定性和速度滿足要求就行了。三年半運行情況表明,系統有良好的處理速度、很好的穩定性、滿足了部省要求。
C# Socket通信三大問題的基本內容就向你介紹到這里了,希望對你了解和學習C# Socket通信三大問題有所幫助。
【編輯推薦】