前几个月有个项目需要和其他设备通信,需要用到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
请记住以下几个数字或信息:
- 消息头部包含三个元素:开始,类型和消息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 }
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作为服务端
- 新建控制台程序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 }
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吧.