這篇廢話不多說,直接上代碼。
首先說明,通訊過程中的異常均不進行處理(串連異常除外),由逾時重發控制。
一、擷取SOCKET串連類TimeOutSocket
public class TimeOutSocket { private static bool IsConnectionSuccessful = false;//串連是否成功 private static Exception socketexception; private static ManualResetEvent TimeoutObject = new ManualResetEvent(false); public static Socket Connect(IPAddress ipAddress, int port, int timeoutMSec) { TimeoutObject.Reset();//將事件設為非終止狀態,阻止線程 socketexception = null; Socket temp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //非同步串連 temp.BeginConnect(new IPEndPoint(ipAddress, port), new AsyncCallback(CallBackMethod), temp); if (TimeoutObject.WaitOne(timeoutMSec, false)) { //在連線逾時時間範圍內等待訊號量為真則返回SOCKET if (IsConnectionSuccessful) { return temp; } else { throw new TimeoutException("串連遠程主機失敗!請檢查網路連接是否通暢。"); } } else { //關閉串連,並釋放所有相關資源 temp.Close(); throw new TimeoutException("連線逾時!請檢查網路連接是否通暢。"); } } /// <summary> /// 非同步串連成功後回呼函數 /// </summary> /// <param></param> private static void CallBackMethod(IAsyncResult asyncresult) { try { IsConnectionSuccessful = false; Socket client = asyncresult.AsyncState as Socket; //結束掛起的非同步串連請求 client.EndConnect(asyncresult); IsConnectionSuccessful = true; } catch (Exception ex) { IsConnectionSuccessful = false; socketexception = ex; } finally { //允許其它線程繼續 TimeoutObject.Set(); } }}
二、通訊變數(常量)
private const int ConnectTimeOut = 6000;//連線逾時時間,以ms為單位private byte[] data = new byte[1024];//接收位元組數組private int recvSize = 1024;//接收資料個數private static int XH = 1;//發送的命令序號 //下面兩個類庫ConvertLibrary和DealProtocolData是我封裝的類庫,在後續中將逐一給出ConvertLibrary convertData = new ConvertLibrary();//對資料提供轉換支援DealProtocolData dealProtocolData = new DealProtocolData();//按要求處理協議結果
三、委託
委託,實質上就是指向函數的指標,也是一個類。
delegate void DealRecvMsgHandler(string str);//用於處理接收到的資料
上面一句用ILDASM反編譯後如下:
可以看出,系統預設產生了其建構函式(.ctor是建構函式),以及成員函數BeginInvoke、EndInvoke以及Invoke。自己定義的委託繼承於System.MulticastDelegate(反編譯中extends看出)。
四、非同步接收資料回呼函數ReceivData()
/// <summary> ///非同步接收資料回調 /// </summary> /// <param></param> private void ReceivData(IAsyncResult iar) { Socket remote = (Socket)iar.AsyncState; int recv = 0; try { recv = remote.EndReceive(iar);//異常:遠程主機強迫關閉了一個現有的串連 } catch { } string tempStr = convertData.ByteToHex(data, recv);//位元組數組轉化成16進位 if (tempStr.Length > 0) { // DealRecvMsg資料處理函數 DealRecvMsgHandler handler = new DealRecvMsgHandler(DealRecvMsg); handler.Invoke(tempStr);//同步資料處理 } else { //由於用了第三方的串口轉網口模組,所以有時候會收到空的情況,這時繼續接收 try { remote.BeginReceive(data, 0, recvSize, SocketFlags.None, new AsyncCallback(ReceivData), PublicCommunication.client); } catch { } } }
五、非同步發送資料回呼函數(SendData)
/// <summary> ///非同步發送資料回調 /// </summary> /// <param></param> private void SendData(IAsyncResult iar) { Socket remote = (Socket)iar.AsyncState; try { int send = remote.EndSend(iar); currentSendedOrderDate = DateTime.Now;//記錄下發送時間,用於重發時使用 if (listenerTimer.Enabled == false)// listenerTimer用於逾時重發 listenerTimer.Enabled = true; currentSendedOrderEnum = GetSendedOrderType(sendMsg);//擷取已發送命令類型 currentSendedOrderXH = GetSendedOrderXH(sendMsg);//擷取已發送命令序號 //發送後,非同步接收 remote.BeginReceive(data, 0, recvSize, SocketFlags.None, new AsyncCallback(ReceivData), PublicCommunication.client); } catch { } }
六、接收資料處理函數(只判斷命令的合法性,決定下一個命令。具體的資料需要另需委託進行處理。)
private void DealRecvMsg(string orgTempStr) {//這裡面是一個while迴圈,迴圈的條件是命令長度大於某個最小指,每次迴圈按命令包中命令長度取出命令,並將剩餘的接收到的命令用於迴圈。//由於通訊追循一定的流程,所以在這裡由接收到的命令可以決定下一個命令//大致給一下這裡面的程式 while (orgTempStr.Length >= 12) { try { string tempStr = orgTempStr.Substring(0, int.Parse(convertData.ConvertString(orgTempStr.Substring(2, 2), 16, 10)) * 2);//取出一條命令 orgTempStr = orgTempStr.Substring(int.Parse(convertData.ConvertString(orgTempStr.Substring(2, 2), 16, 10)) * 2);// orgTempStr保留剩餘的命令 //判斷資料合法性:根據協議接收到的資料長度至少為12,而且保證接收到的資料無誤(校正碼), GetStrXOR()函數是擷取校正碼,後面給出 if (tempStr.Length >= 12 && dealProtocolData.GetStrXOR(tempStr.Substring(0, tempStr.Length - 2)).Equals(tempStr.Substring(tempStr.Length - 2), StringComparison.OrdinalIgnoreCase)) { string workState = tempStr.Substring(8, 2);//擷取儀器工作狀態 string order = tempStr.Substring(4, 2);//擷取命令字 //如果不是上次發送的命令則丟棄當前資料 if (!JudgeReceiveOrderWithSendOrder(order, currentSendedOrderEnum)) continue; //命令序號不同則丟棄 if (!(int.Parse(convertData.ConvertString(tempStr.Substring(6, 2), 16, 10)) == currentSendedOrderXH)) continue; currentSendedOrderDate = DateTime.MinValue;//收到命令後,設定命令的發送時間為DateTime的最小值 ReSendTimeCount = -1;//逾時重發次數 //"00"應該定義成常量,方便使用時如果"00"改為”ff”時不用滿程式中修改 if (workState.Equals("00", StringComparison.OrdinalIgnoreCase))//儀器正常工作 {if (order.Equals(ProtocolContent.SetParamsCommandWord, StringComparison.OrdinalIgnoreCase))//設定參數命令的返回 {//決定下一個要發送的命令sendMsg =……;}………..……….. byte[] msg = convertData.HexToByte(sendMsg); try { //非同步發送資料 PublicCommunication.client.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendData), PublicCommunication.client); //處理髮送序號,保證發送序號在1-255之間 XH = (XH + 1) % 256; if (XH == 0) XH = 1; } catch { } }
七、逾時重發
/// <summary> /// 命令的枚舉值 /// </summary> public enum OrderEnum { UnKnown = 0 } private static string sendMsg = String.Empty;//記錄每次發送的命令private static OrderEnum currentSendedOrderEnum = OrderEnum.UnKnown;//記錄本次發送的命令的類型private static int currentSendedOrderXH = -1;//當前發送命令的序號private static DateTime currentSendedOrderDate = DateTime.MinValue;//最近一次發送命令的時間private System.Timers.Timer listenerTimer;//用於監控命令發送的定時器private static int ReSendTimeCount = -1;//重新發送命令的次數 listenerTimer = new System.Timers.Timer(25);listenerTimer.Elapsed += new System.Timers.ElapsedEventHandler(listenerTimer_Elapsed);listenerTimer.Enabled = false; private void listenerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (currentSendedOrderDate != DateTime.MinValue)//當沒有收到資料時 { double timeInterval = (DateTime.Now - currentSendedOrderDate).TotalMilliseconds; if (timeInterval >= 1000)//1S重發 { ReSendTimeCount++; if (ReSendTimeCount >= 3) { //逾時處理…….. return; }//未逾時三次,繼續重發 SendMsgWhenTimeOut(sendMsg); } } }
八、通訊結束或者逾時後的處理
當無通訊時,用戶端關閉,伺服器是檢測不到的,所以我在做的時候,一次通訊結束或者逾時後都會將串連關閉共置為空白。
private void WhenCommunicateOver(){listenerTimer.Enabled = false;try { PublicCommunication.client.Close(); } catch { } finally { PublicCommunication.client = null; }}
九、嘗試三次串連
private void startCommunication() { int connectTime = 0; do { try { PublicCommunication.client = TimeOutSocket.Connect(IPAddress.Parse(PublicModel.InstrumentIP), PublicModel.InstrumentPort, ConnectTimeOut); break; } catch (Exception ec) { connectTime++; PublicCommunication.client = null; if (connectTime >= 3) { WhenCommunicateOver(); lock (whetherShowErrorMsg) { if (!((bool)whetherShowErrorMsg)) { MessageBox.Show(this, ec.Message, "系統提示", MessageBoxButtons.OK, MessageBoxIcon.Error); whetherShowErrorMsg = true; } } Application.DoEvents(); return; } } } while (connectTime < 4);}