Socket異步通信及心跳包同時響應邏輯分析(最後附Demo)。

  有段時間沒有更博了,剛好最近在做Socket通信的項目,原理大致內容:【二維碼-(加logo)】-->提供主機地址和端口號信息(直接使用【ThoughtWorks.QRCode.dll】比較簡單就不贅述了,核心方法直接貼出來)。然後使用手機APP掃描進行連接服務器,然後通過TCP/IP協議進行握手傳輸,接收到的圖片按照一定的規則進行排列。實時使用心跳包進行檢測,服務器進行實時響應。

一、二維碼+logo核心方法:

引用的命名空間是:using ThoughtWorks.QRCode.Codec;隨便用一個控件(比如:Image就可以show出來!)

 1  /// <summary>
 2         /// 初始化二維碼對象並根據傳入的信息進行創建
 3         /// </summary>
 4         public Bitmap Inilized_QRCode(string Imformation)
 5         {
 6             encoder.QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE;//編碼方式(注意:BYTE能支持中文,ALPHA_NUMERIC掃描出來的都是數字)
 7             encoder.QRCodeScale = 10;//大小(值越大生成的二維碼圖片像素越高)
 8             encoder.QRCodeVersion = 0;//版本(注意:設置爲0主要是防止編碼的字符串太長時發生錯誤)
 9             encoder.QRCodeErrorCorrect = QRCodeEncoder.ERROR_CORRECTION.M;//錯誤效驗、錯誤更正(有4個等級)
10             Bitmap bp = encoder.Encode(Imformation, Encoding.GetEncoding("GB2312"));//進行位圖編碼
11             Image image = bp;
12             return bp;
13         }
二維碼+logo

二、重點分析心跳包與握手協議:

本次採用的是Socket進行異步傳輸,首先要定義服務器地址和端口號(區分網路上其他主機的唯一標識);開始之前先申明:本文采用的機制是一個客戶端只使用一個Socket,服務器通過端口進行監聽,併發響應客戶端。

服務器

A、定義Socket 和 獲取 監聽服務器本機地址 端口號

1  //監聽初始化
2         private Socket listener = null;//連接客戶端
3         private Socket RemoteClient = null;//接收消息
4 
5         private int port = 8089;
6         private IPEndPoint listenEP = null;

B、定義好之後開始初始化數據 ,使用AcceptCallback 進行消息回調並等待客戶端觸發接收數據

 1   private void RunsSocketServer()
 2         {
 3             listener = new Socket(
 4                  AddressFamily.InterNetwork,
 5                  SocketType.Stream,
 6                  ProtocolType.Tcp);
 7             listener.Bind(listenEP);
 8             listener.Listen(10);
 9             listener.BeginAccept(
10                 new AsyncCallback(AcceptCallback),
11                 null);
12 
13             LB_TXT.Content = "監聽已開啓" + listener.LocalEndPoint.ToString() + "";
14 
15         }

C、回調函數

 1  private void AcceptCallback(IAsyncResult iar)
 2         {
 3             try
 4             {
 5                 RemoteClient = listener.EndAccept(iar);
 6                 //同進程上的線程異步
 7                 this.Dispatcher.BeginInvoke((MethodInvoker)delegate ()
 8                 {
 9                     LB_TXT.Content = string.Format("{0}已連接到服務器", RemoteClient.RemoteEndPoint.ToString());
10                 });
11 
12                 ReceivePicHandShakes();//握手協議及接收圖片
13 
14             }
15             catch (Exception err)
16             {
17                 this.Dispatcher.BeginInvoke((MethodInvoker)delegate ()
18                  {
19                      LB_TXT.Content = err.Message;
20                  });
21                 return;
22             }
23         }

D、通過開闢一個新的線程進行異步接收數據(圖中的協議使用XXX代替,請大家根據自己的協議需要進行定義)

  1   Thread _timers = null;
  2         private Socket _SocketClient = null;
  3         byte[] RecvBytes = new byte[50];
  4         private int Numimgs = 0;//記錄接收成功的張數
  5         //byte[] ReImgBytes = new byte[8];//接收圖片的頭字節
  6         public event ReceivedBitmapHandler ReceivedBitmap;
  7         private delegate void RestartThread();//定義委託
  8 
  9 
 10         public TransferHandler(Socket client)
 11         {
 12             _SocketClient = client;
 13         }
 14 
 15         #region 服務器端
 16         public void BeginReceive()
 17         {
 18             //採用線程直接接收的方式
 19             _timers = new Thread(StartToReceive);
 20             _timers.IsBackground = true;
 21             _timers.Start();
 22         }
 23         /// <summary>
 24         /// 開始接收消息
 25         /// </summary>
 26         private void StartToReceive()
 27         {
 28             //由於long佔8位字節,所以先獲取前8位字節數據
 29             IAsyncResult iar = _SocketClient.BeginReceive(
 30                 RecvBytes,
 31                 0,
 32                 RecvBytes.Length,
 33                 SocketFlags.None,
 34                 null,
 35                 null);
 36             int len = _SocketClient.EndReceive(iar);
 37             string ReceivemMsg = Encoding.ASCII.GetString(RecvBytes, 0, len);
 38             if (ReceivemMsg.IndexOf("xxx") > 0)//區分業務消息和心跳檢測消息
 39             {
 40                 _SocketClient.Send(Encoding.ASCII.GetBytes("xxx"));//迴應心跳包
 41                 RecvBytes = new byte[RecvBytes.Length]; //清空數據
 42                 StartToReceive();//迴應心跳包完成之後繼續等待接收
 43             }
 44             else if (ReceivemMsg == "xxx")//如果收到這個請求,告訴客戶端可以開始發送第一張圖片了
 45             {
 46                 string order = "xxx";
 47                 byte[] orderdata = Encoding.ASCII.GetBytes(order);   //把字符串編碼爲字節
 48                 _SocketClient.Send(orderdata);
 49                 RecvBytes = new byte[8]; //開始接收圖片
 50                 StartToReceive();//迴應指令後完成之後繼續等待接收
 51             }
 52             else if (ReceivemMsg == "xxx")
 53             {
 54                 //停止接收圖片
 55                 string order = "xxxx";//通知客戶端已經完成接收這次所有圖片,結束傳輸;
 56                 byte[] orderdata = Encoding.ASCII.GetBytes(order);   //把字符串編碼爲字節
 57                 _SocketClient.Send(orderdata);
 58                 StartToReceive();
 59             }
 60             else
 61             {
 62                 int offset = 0;
 63                 int length = BitConverter.ToInt32(RecvBytes, offset);  //先獲取文件長度
 64                 ReceiveFile(length);
 65             }
 66 
 67         }
 68 
 69 
 70         public void ReceiveFile(long filelen)
 71         {
 72             MemoryStream ms = new MemoryStream();
 73             int bytesRead = 0;
 74             long count = 0;
 75             byte[] buffer = new byte[8192];
 76 
 77             while (count != filelen)
 78             {
 79                 try
 80                 {
 81                     bytesRead = _SocketClient.Receive(buffer);
 82                     ms.Write(buffer, 0, bytesRead);
 83                     count += bytesRead;
 84                 }
 85                 catch (Exception ex)
 86                 {
 87 
 88                 }
 89             }
 90             ReceivedBitmap(new Bitmap(ms));
 91 
 92 
 93             //接收完成之後清空數據,繼續接收
 94             buffer = new byte[buffer.Length];//緩存
 95             RecvBytes = new byte[RecvBytes.Length];//用於接收
 96 
 97             string order = "xxxx";//通知客戶端已經收到圖片,請繼續
 98             byte[] orderdata = Encoding.ASCII.GetBytes(order);   //把字符串編碼爲字節
 99             _SocketClient.Send(orderdata);
100             Numimgs++;
101 
102             if (Numimgs >= 3)
103             {
104                 RecvBytes = new byte[50];
105                 Numimgs = 0;
106             }
107             StartToReceive(); //接收完成之後繼續接收
108         }
109         #endregion

* 客戶端

A、心跳包發送(代碼中XXX解釋同上)

 1         #region 心跳檢測
 2         private void HeartBeatsTests()
 3         {
 4             Thread sendEcho = new Thread(new ThreadStart(socketSend));
 5             sendEcho.Start();
 6             sendEcho.IsBackground = true;
 7         }
 8 
 9 
10         public void socketSend()
11         {
12             while (true)
13             {
14                 Thread.Sleep(10000);//每十秒發一次,響應發3次
15                 //備註:IsSendingImgs必須放在sleep底下,防止等待的空餘時間發送導致服務端繼續接收心跳包
16                 if (!IsSendingImgs)//如果是任務還在執行不允許第二次發送,必須要等待完成
17                 {
18                     try
19                     {
20                         this.Dispatcher.BeginInvoke((MethodInvoker)delegate ()
21                         {
22                             this.richTextBox.AppendText("開始發送心跳包..." + Environment.NewLine);
23                         });
24                         client.Send(Encoding.ASCII.GetBytes("xxxx"));//發送心跳暗號
25                     }
26                     catch (SocketException)
27                     {
28                         Thread.CurrentThread.Abort();
29                         //throw ex;
30                     }
31                     //標記發送的次數
32                     this.MyLostTime++;
33                     //如果外發了3次請求暗號後仍不見服務器的迴應,則認爲客戶端已經與服務器斷開聯繫了
34                     if (this.MyLostTime >= 3)
35                     {
36                         //IsSendingImgs = true;
37                         TimeSpan t = DateTime.Now - lastConnect;
38                         if (t.TotalSeconds > 30)
39                         {
40                             this.Dispatcher.BeginInvoke((MethodInvoker)delegate ()
41                             {
42                                 //this.richTextBox.cont
43                                 this.richTextBox.AppendText("與服務器失去聯繫..." + Environment.NewLine);
44                             });
45                         }
46                         else
47                         {
48 
49                             this.countnum++;
50                             this.Dispatcher.BeginInvoke((MethodInvoker)delegate ()
51                             {
52                                 //this.richTextBox.cont
53                                 this.richTextBox.AppendText("服務器響應第" + countnum.ToString() + "次,響應時間是:" + DateTime.Now.ToString() + "" + Environment.NewLine);
54                             });
55                         }
56                         //每次確定後重置
57                         this.MyLostTime = 0;
58                     }
59                 }
60                 else
61                 {
62                     lastConnect = DateTime.Now;//每十秒刷新,防止圖片傳送任務完成之後導致時間錯誤;
63                 }
64             }
65 
66         }
67 
68         #endregion

B、響應機制(發送圖片及接收消息觸發停止、開啓心跳包代碼中XXX解釋同上)

 1         /// <summary>
 2         /// 協議請求
 3         /// </summary>
 4         public void SendSocketData()
 5         {
 6             string sendStr = "xxx"; //向服務器發送請求:發送3張圖片的指令
 7             byte[] buffer = Encoding.ASCII.GetBytes(sendStr);   //把字符串編碼爲字節
 8             _SendQuest.Send(buffer); //發送
 9             _SendQuest.BeginReceive(Rbuffer, 0, Rbuffer.Length, 0, new AsyncCallback(CallbackRuquest), null);//等待接收並回調
10         }
11 
12 
13         /// <summary>
14         /// 回調告訴socket可以執行下一步了
15         /// </summary>
16         /// <param name="iar"></param>
17         private void CallbackRuquest(IAsyncResult arr)
18         {
19             int aa = _SendQuest.EndReceive(arr);
20             string Rc_Msg = Encoding.ASCII.GetString(Rbuffer, 0, aa);
21             if (Rc_Msg == "xxx")
22             {
23                 khd.IsSendingImgs = false;//啓動心跳包--用於接收完成之後繼續開啓心跳包用
24                 Numimgs = 0;
25                 _SendQuest.BeginReceive(Rbuffer, 0, Rbuffer.Length, 0, new AsyncCallback(CallbackRuquest), null);//等待接收並回調
26             }
27             else
28             {
29                 //結束接收
30                 if (Numimgs >= 3)
31                 {
32                     //結束
33                     byte[] buffer = Encoding.ASCII.GetBytes("xxx");
34                     _SendQuest.Send(buffer);
35                     _SendQuest.BeginReceive(buffer, 0, buffer.Length, 0, new AsyncCallback(CallbackRuquest), null);//等待接收並回調
36                   
37                 }
38                 else
39                 {
40                     //接收成功並進行回調
41 
42                     if (Rc_Msg.IndexOf("xxx") > 0)
43                     {
44                         Socket_Client.lastConnect = DateTime.Now;//重新賦值
45                         khd.Dispatcher.BeginInvoke((MethodInvoker)delegate ()
46                         {
47                             khd.richTextBox.AppendText("接收到服務器的響應消息" + Environment.NewLine);
48                         });
49                         _SendQuest.BeginReceive(Rbuffer, 0, Rbuffer.Length, 0, new AsyncCallback(CallbackRuquest), null);//等待接收並回調
50                     }
51                     else if (Rc_Msg == "xxx" || Rc_Msg == "xxx")
52                     {
53                         FileInfo fi = new FileInfo(Filename);//獲取文件
54                         byte[] len = BitConverter.GetBytes(fi.Length);//文件數據
55                                                                       //首先把文件長度發送過去
56                         _SendQuest.BeginSendFile(Filename, len, null, TransmitFileOptions.UseDefaultWorkerThread, new AsyncCallback(EndSendback), null);
57                         _SendQuest.BeginReceive(Rbuffer, 0, Rbuffer.Length, 0, new AsyncCallback(CallbackRuquest), null);//等待接收並回調
58                                                                                                                          //清空數據
59                         len = new byte[fi.Length];
60                         Numimgs++;
61                     }
62                 }
63             }
64         }
65 
66         /// <summary>
67         /// 結束髮送 
68         /// </summary>
69         /// <param name="eof"></param>
70         private void EndSendback(IAsyncResult eof)
71         {
72             _SendQuest.EndSendFile(eof);
73         }
74 
75         /// <summary>
76         /// 結束接收 
77         /// </summary>
78         /// <param name="eof"></param>
79         private void EndCallback(IAsyncResult eof)
80         {
81             //結束接收
82             _SendQuest.EndReceive(eof);
83 
84         }

這邊只着重做出如何響應如何發送的邏輯實現,做個筆記。還是蠻有意思的。以下是Demo效果圖:

 

此文的Demo下載地址:Socket心跳通信

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章