前幾個月有個專案需要和其他裝置通訊,需要用到TCP和UDP通訊.本來開始也是用的C#原生態socket通訊庫,但是後來發現了一個”我不想說他名字坑爹庫”,經過測試,用起來還挺順手,就直接把這個”我不想說他名字坑爹庫”引入了專案中.還把使用方法寫在了部落格園,測試demo還上傳了程式碼(通訊庫並沒有原始碼,只有dll檔案).
結果,大約三個月後,有人給我留言,說他下載了”我不想說他名字坑爹庫”的demo原始碼,並且引入了專案中,但是要收費,已經過期,他們的專案已經掛了.開始我還不信((當時我在出差,其實我自己專案中的這個坑爹庫也過期了),一直以為”我不想說他名字坑爹庫”是免費的,是他們專案中的其他庫出問題了,後來發現,這個庫確實要收費的,並且還價格不菲.我簡直*****了.
幸好我們的專案還沒上線,不然真的要死翹翹了,丟人要丟到外國去了.不過下載我原始碼的兄弟就真的對不起了.我在這裡真誠的為你們感到sorry.
後來嘛,就只能換噻,好在程式碼框架還行,通訊和業務耦合度很低,換通訊框架很容易.在這個開源大行其道的世界,遇到收費的庫,我也是無語.再後來,就找到SuperSocket了,開源的,老闆再也不擔心軟體過期了.
這個文章就不介紹SuperSocket的其他東西了,只寫FixedHeaderReceiveFilter通訊例子.
需求:一個tcp服務端,多個tcp客戶端,客戶端可向服務端傳送訊息,服務端向連線的每個客戶端廣播訊息.
1.定義訊息格式
要傳送的訊息格式直接給出來:
1 Public class SuperSocketMessage 2 { 3 public byte[] Start;//4個位元組 4 public ushort Type;//2個位元組, 1表示文字訊息,2表示圖片訊息,其他表示未知,不能解析 5 public int Len;//4個位元組 6 public string Message;//文字資訊 7 public byte[] Tail;//訊息結尾 8 9 public SuperSocketMessage() 10 { 11 Start = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; 12 Tail = new byte[] { 0x1F, 0x1F, 0x1F, 0x1F }; 13 } 14 15 public byte[] ToBytes() 16 { 17 List<byte> list = new List<byte>(); 18 list.AddRange(Start); 19 var t = BitConverter.GetBytes(Type); 20 list.AddRange(t); 21 22 var t3 = System.Text.Encoding.UTF8.GetBytes(Message); 23 var t2 = BitConverter.GetBytes(t3.Length);//注意,這裡不是Message.Length,而是Message轉化成位元組陣列後的Lenght 24 25 list.AddRange(t2); 26 list.AddRange(t3); 27 28 return list.ToArray(); 29 } 30 }
請記住以下幾個數字或資訊:
- 訊息頭部包含三個元素:開始,型別和訊息body的長度
- 從第六個位元組開始表示長度
- 表示長度位元組數是4個位元組
- Body的長度不要搞混,不是Message的字串長度,而是Message轉換位元組陣列後的長度,除錯的時候,開始用了Message的字串長度,給我造成了幾個小時的浪費.
- 以上資訊請和下面的MyReceiveFilter類中的引數對照看.
2.建立一個tcp客戶端
1.新建一個控制檯程式TcpClientTest,這裡為了測試的客觀性,直接使用C#的TcpClient作為客戶端.
2.新增Class: MyTcpClient.cs
1 class MyTcpClient 2 { 3 private System.Net.Sockets.TcpClient tcpClient; 4 public MyTcpClient(string ip, int port) 5 { 6 7 tcpClient = new System.Net.Sockets.TcpClient(ip, port); 8 byte[] recData = new byte[1024]; 9 Action a = new Action(() => 10 { 11 while (true) 12 { 13 tcpClient.Client.Receive(recData); 14 var msg = System.Text.Encoding.UTF8.GetString(recData); 15 Console.WriteLine(msg); 16 } 17 }); 18 a.BeginInvoke(null, null); 19 20 } 21 22 public void Send(string message) 23 { 24 var data = System.Text.Encoding.UTF8.GetBytes(message); 25 tcpClient.Client.Send(data); 26 } 27 public void Send(byte[] message) 28 { 29 tcpClient.Client.Send(message); 30 } 31 }
3.Main函式:
1 static void Main(string[] args) 2 { 3 MyTcpClient c = new MyTcpClient("127.0.0.1", 2020); 4 SuperSocketMessage.SSMessage msg = new SuperSocketMessage.SSMessage(); 5 while (true) 6 { 7 string m = Console.ReadLine(); 8 msg.Type = 1; 9 msg.Message = m; 10 c.Send(msg.ToBytes()); 11 } 12 }
客戶端實現控制檯輸入任意字串後,封裝成訊息報文,發給服務端;同時,開啟一個執行緒,接收服務端的訊息.這裡客服端就沒有按照標準來解析了.直接定義緩衝區1024.
3.SuperSocket作為服務端
- 新建控制檯程式SuperSocketServer
- 使用nuget工具安裝SuperSocket和SuperSocket.Engine庫
- 實現3個類: Filter,Session和Server
Filter類如下:
1 public class MyReceiveFilter : FixedHeaderReceiveFilter<BinaryRequestInfo> 2 { 3 4 public MyReceiveFilter() 5 : base(10)//訊息頭部長度 6 { } 7 /// <summary> 8 /// 9 /// </summary> 10 /// <param name="header">*byte[] header * 快取的資料,這個並不是單純只包含協議頭的資料,有時候tcp協議長度為409600,很多</param> 11 /// <param name="offset">頭部資料從 快取的資料 中開始的索引,一般為0.(tcp協議有可能從405504之類的一個很大資料開始)</param> 12 /// <param name="length">這個length和base(10)中的引數相等</param> 13 /// <returns></returns> 14 protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length) 15 { 16 return GetBodyLengthFromHeader(header, offset, length, 6, 4);//6表示第幾個位元組開始表示長度.4:由於是int來表示長度,int佔用4個位元組 17 } 18 protected override BinaryRequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length) 19 { 20 21 byte[] body = new byte[length]; 22 Array.Copy(bodyBuffer, offset, body, 0, length); 23 24 Int16 type = BitConverter.ToInt16(header.ToArray(), 4); 25 26 BinaryRequestInfo r = new BinaryRequestInfo(type.ToString(), body); 27 return r; 28 29 //以下的程式碼,不解析body,全部返給上一層 30 //byte[] h = header.ToArray(); 31 //byte[] full = new byte[h.Count()+length]; 32 //Array.Copy(h, full, h.Count()); 33 //Array.Copy(body, 0, full, h.Count(), body.Length ); 34 //BinaryRequestInfo r = new BinaryRequestInfo("",full); 35 //return r; 36 37 } 38 39 /// <summary> 40 /// 41 /// </summary> 42 /// <param name="header">需要解析的資料</param> 43 /// <param name="offset">頭部資料從header中開始的索引,一般為0,也可能不是0</param> 44 /// <param name="length">這個length和base(10)中的引數相等</param> 45 /// <param name="lenStartIndex">表示長度的位元組從第幾個開始</param> 46 /// <param name="lenBytesCount">幾個位元組來表示長度:4個位元組=int,2個位元組=int16,1個位元組=byte</param> 47 /// <returns></returns> 48 private int GetBodyLengthFromHeader(byte[] header, int offset, int length, int lenStartIndex, int lenBytesCount) 49 { 50 var headerData = new byte[lenBytesCount]; 51 Array.Copy(header, offset + lenStartIndex, headerData, 0, lenBytesCount);// 52 if (lenBytesCount == 1) 53 { 54 int i = headerData[0]; 55 return i; 56 } 57 else if (lenBytesCount == 2) 58 { 59 int i = BitConverter.ToInt16(headerData, 0); 60 return i; 61 } 62 else // if (lenBytesCount == 4) 63 { 64 int i = BitConverter.ToInt32(headerData, 0); 65 return i; 66 } 67 } 68 }
Server和Session類如下:
1 public class MyServer : AppServer<MySession, BinaryRequestInfo> 2 { 3 public MyServer() 4 : base(new DefaultReceiveFilterFactory<MyReceiveFilter, BinaryRequestInfo>()) //使用預設的接受過濾器工廠 (DefaultReceiveFilterFactory) 5 { 6 } 7 } 8 9 public class MySession : AppSession<MySession, BinaryRequestInfo> 10 { 11 }
BinaryRequestInfo實現了 IRequestInfo介面,這個介面就一個key,貌似SuperSocket的所有資料都是實現這個介面
4.Main函式關鍵程式碼:
配置:
1 MyServer appServer = new MyServer(); 2 var se = new SuperSocket.SocketBase.Config.ServerConfig(); 3 se.TextEncoding = "Unicode";// System.Text.Encoding. 4 se.TextEncoding = "gbk";// System.Text.Encoding. 5 se.Ip = "127.0.0.1"; 6 se.Port = 2020; 7 se.Mode = SocketMode.Tcp;
註冊,啟動:
1 if (!appServer.Setup(se)) //Setup with listening port 2 { 3 Console.WriteLine("Failed to setup!"); 4 Console.ReadKey(); 5 return; 6 } 7 Console.WriteLine(); 8 //Try to start the appServer 9 if (!appServer.Start()) 10 { 11 Console.WriteLine("Failed to start!"); 12 Console.ReadKey(); 13 return; 14 }
註冊事件:
1 appServer.NewSessionConnected += appServer_NewSessionConnected; 2 appServer.SessionClosed += appServer_SessionClosed; 3 appServer.NewRequestReceived += XXXXXXXXXXXXXXXXXXX
解析方法:
1 static void appServer_NewRequestReceived(MySession session, BinaryRequestInfo requestInfo) 2 { 3 string key = requestInfo.Key; 4 switch (key) 5 { 6 case "1": 7 Console.WriteLine("Get message from " + session.RemoteEndPoint.ToString() + ":" + System.Text.Encoding.UTF8.GetString(requestInfo.Body)); 8 break; 9 case "2": 10 Console.WriteLine("Get image"); 11 break; 12 default: 13 Console.WriteLine("Get unknown message."); 14 break; 15 } 16 }
好.完成.截圖如下:
附原始碼下載地址:https://download.csdn.net/download/hanghangz/11236794
專案中用nuget下載packages中的內容沒有上傳,檔案太大了,自己去nuget吧.