使用SuperSocket的FixedHeaderReceiveFilter進行通訊

瘦馬發表於2019-06-12

前幾個月有個專案需要和其他裝置通訊,需要用到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 }
View Code

請記住以下幾個數字或資訊:

  1. 訊息頭部包含三個元素:開始,型別和訊息body的長度
  2. 從第六個位元組開始表示長度
  3. 表示長度位元組數是4個位元組
  4. Body的長度不要搞混,不是Message的字串長度,而是Message轉換位元組陣列後的長度,除錯的時候,開始用了Message的字串長度,給我造成了幾個小時的浪費.
  5. 以上資訊請和下面的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 }
View Code

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         }
View Code

客戶端實現控制檯輸入任意字串後,封裝成訊息報文,發給服務端;同時,開啟一個執行緒,接收服務端的訊息.這裡客服端就沒有按照標準來解析了.直接定義緩衝區1024.

3.SuperSocket作為服務端

  1. 新建控制檯程式SuperSocketServer
  2. 使用nuget工具安裝SuperSocket和SuperSocket.Engine庫
  3. 實現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 }
View Code

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     }
View Code

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;
View Code

註冊,啟動:

 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吧.

相關文章