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