前幾個月有個項目需要和其他裝置通信,需要用到TCP和UDP通信.本來開始也是用的C#原生态socket通信庫,但是後來發現了一個”我不想說他名字坑爹庫”,經過測試,用起來還挺順手,就直接把這個”我不想說他名字坑爹庫”引入了項目中.還把使用方法寫在了部落格園,測試demo還上傳了代碼(通訊庫并沒有源碼,隻有dll檔案).
結果,大約三個月後,有人給我留言,說他下載下傳了”我不想說他名字坑爹庫”的demo源碼,并且引入了項目中,但是要收費,已經過期,他們的項目已經挂了.開始我還不信((當時我在出差,其實我自己項目中的這個坑爹庫也過期了),一直以為”我不想說他名字坑爹庫”是免費的,是他們項目中的其他庫出問題了,後來發現,這個庫确實要收費的,并且還價格不菲.我簡直*****了.
幸好我們的項目還沒上線,不然真的要死翹翹了,丢人要丢到外國去了.不過下載下傳我源碼的兄弟就真的對不起了.我在這裡真誠的為你們感到sorry.
後來嘛,就隻能換噻,好在代碼架構還行,通信和業務耦合度很低,換通信架構很容易.在這個開源大行其道的世界,遇到收費的庫,我也是無語.再後來,就找到SuperSocket了,開源的,老闆再也不擔心軟體過期了.
這個文章就不介紹SuperSocket的其他東西了,隻寫FixedHeaderReceiveFilter通信例子.
需求:一個tcp服務端,多個tcp用戶端,用戶端可向服務端發送消息,服務端向連接配接的每個用戶端廣播消息.
1.定義消息格式
要發送的消息格式直接給出來:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pml2Zu0GbyoVdzJTW2hXbRtmVHRmaG12YwUjMiRUO5NWe5cEZo50VhtWNXNlb1cVY1x2RiBjVzQldwIjYqVTej5WOHJWa1ITW11EWa5mRXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.gif)
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 }
View Code
請記住以下幾個數字或資訊:
- 消息頭部包含三個元素:開始,類型和消息body的長度
- 從第六個位元組開始表示長度
- 表示長度位元組數是4個位元組
- Body的長度不要搞混,不是Message的字元串長度,而是Message轉換位元組數組後的長度,調試的時候,開始用了Message的字元串長度,給我造成了幾個小時的浪費.
- 以上資訊請和下面的MyReceiveFilter類中的參數對照看.
2.建立一個tcp用戶端
1.建立一個控制台程式TcpClientTest,這裡為了測試的客觀性,直接使用C#的TcpClient作為用戶端.
2.添加Class: MyTcpClient.cs
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pml2Zu0GbyoVdzJTW2hXbRtmVHRmaG12YwUjMiRUO5NWe5cEZo50VhtWNXNlb1cVY1x2RiBjVzQldwIjYqVTej5WOHJWa1ITW11EWa5mRXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.gif)
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 }
View Code
3.Main函數:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pml2Zu0GbyoVdzJTW2hXbRtmVHRmaG12YwUjMiRUO5NWe5cEZo50VhtWNXNlb1cVY1x2RiBjVzQldwIjYqVTej5WOHJWa1ITW11EWa5mRXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.gif)
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 }
View Code
用戶端實作控制台輸入任意字元串後,封裝成消息封包,發給服務端;同時,開啟一個線程,接收服務端的消息.這裡客服端就沒有按照标準來解析了.直接定義緩沖區1024.
3.SuperSocket作為服務端
- 建立控制台程式SuperSocketServer
- 使用nuget工具安裝SuperSocket和SuperSocket.Engine庫
- 實作3個類: Filter,Session和Server
Filter類如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pml2Zu0GbyoVdzJTW2hXbRtmVHRmaG12YwUjMiRUO5NWe5cEZo50VhtWNXNlb1cVY1x2RiBjVzQldwIjYqVTej5WOHJWa1ITW11EWa5mRXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.gif)
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 }
View Code
Server和Session類如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pml2Zu0GbyoVdzJTW2hXbRtmVHRmaG12YwUjMiRUO5NWe5cEZo50VhtWNXNlb1cVY1x2RiBjVzQldwIjYqVTej5WOHJWa1ITW11EWa5mRXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.gif)
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 }
View Code
BinaryRequestInfo實作了 IRequestInfo接口,這個接口就一個key,貌似SuperSocket的所有資料都是實作這個接口
4.Main函數關鍵代碼:
配置:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pml2Zu0GbyoVdzJTW2hXbRtmVHRmaG12YwUjMiRUO5NWe5cEZo50VhtWNXNlb1cVY1x2RiBjVzQldwIjYqVTej5WOHJWa1ITW11EWa5mRXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.gif)
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;
View Code
注冊,啟動:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM9AnYldnJwAzN9c3Pml2Zu0GbyoVdzJTW2hXbRtmVHRmaG12YwUjMiRUO5NWe5cEZo50VhtWNXNlb1cVY1x2RiBjVzQldwIjYqVTej5WOHJWa1ITW11EWa5mRXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.gif)
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 }
View Code
注冊事件:
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吧.