socket里面的协议解析是socket通讯程序设计中最复杂的地方,如果你的应用层协议设计或实现不佳,socket通讯中常见的粘包,分包就难以避免。supersocket内置了命令行格式的协议commandlineprotocol,如果你使用了其它格式的协议,就必须自行实现自定义协议customprotocol。看了一篇文档之后, 你可能会觉得用 supersocket 来实现你的自定义协议并不简单。 为了让这件事变得更容易一些, supersocket 提供了一些通用的协议解析工具, 你可以用他们简单而且快速的实现你自己的通信协议:
- terminatorreceivefilter (supersocket.socketbase.protocol.terminatorreceivefilter, supersocket.socketbase) ---结束符协议
- countspliterreceivefilter (supersocket.facility.protocol.countspliterreceivefilter, supersocket.facility)---固定数量分隔符协议
- fixedsizereceivefilter (supersocket.facility.protocol.fixedsizereceivefilter, supersocket.facility)---固定请求大小协议
- beginendmarkreceivefilter (supersocket.facility.protocol.beginendmarkreceivefilter, supersocket.facility)---带起止符协议
- fixedheaderreceivefilter (supersocket.facility.protocol.fixedheaderreceivefilter, supersocket.facility)---头部格式固定并包含内容长度协议
1、terminatorreceivefilter结束符协议
结束符协议和命令行协议类似,一些协议用结束符来确定一个请求.例如, 一个协议使用两个字符 "##" 作为结束符, 于是你可以使用类 "terminatorreceivefilterfactory":
结束符协议terminatorprotocolserver :
1
2
3
4
5
6
7
|
public class terminatorprotocolserver : appserver { public terminatorprotocolserver() : base ( new terminatorreceivefilterfactory( "##" )) { } } |
基于terminatorreceivefilter实现你的接收过滤器(receivefilter):
1
2
3
4
|
public class yourreceivefilter : terminatorreceivefilter<yourrequestinfo> { //more code } |
实现你的接收过滤器工厂(receivefilterfactory)用于创建接受过滤器实例:
1
2
3
4
|
public class yourreceivefilterfactory : ireceivefilterfactory<yourrequestinfo> { //more code } |
2、countspliterreceivefilter 固定数量分隔符协议
有些协议定义了像这样格式的请求 "#part1#part2#part3#part4#part5#part6#part7#". 每个请求有7个由 '#' 分隔的部分. 这种协议的实现非常简单:
1
2
3
4
5
6
7
8
9
10
|
/// <summary> /// 请求格式:#part1#part2#part3#part4#part5#part6#part7# /// </summary> public class countspliterappserver : appserver { public countspliterappserver() : base ( new countspliterreceivefilterfactory(( byte ) '#' , 8)) //8个分隔符,7个参数。除使用默认的过滤工厂,还可以参照上一个实例定制协议 { } } |
3、fixedsizereceivefilter 固定请求大小协议
在这种协议之中, 所有请求的大小都是相同的。如果你的每个请求都是有8个字符组成的字符串,如"huang li", 你应该做的事就是想如下代码这样实现一个接收过滤器(receivefilter):
1
2
3
4
5
6
7
8
9
10
11
|
class myreceivefilter : fixedsizereceivefilter<stringrequestinfo> { public myreceivefilter() : base (8) //传入固定的请求大小 { } protected override stringrequestinfo processmatchedrequest( byte [] buffer, int offset, int length, bool tobecopied) { //todo: 通过解析到的数据来构造请求实例,并返回 } } |
然后在你的 appserver 类中使用这个接受过滤器 (receivefilter):
1
2
3
4
5
6
7
|
public class myappserver : appserver { public myappserver() : base ( new defaultreceivefilterfactory<myreceivefilter, stringrequestinfo>()) //使用默认的接受过滤器工厂 (defaultreceivefilterfactory) { } } |
4、beginendmarkreceivefilter 带起止符协议
在这类协议的每个请求之中 都有固定的开始和结束标记。例如, 我有个协议,它的所有消息都遵循这种格式 "&xxxxxxxxxxxxxx#"。因此,在这种情况下, "&" 是开始标记, "#" 是结束标记,于是你的接受过滤器可以定义成这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class myreceivefilter : beginendmarkreceivefilter<stringrequestinfo> { //开始和结束标记也可以是两个或两个以上的字节 private readonly static byte [] beginmark = new byte [] { ( byte ) '&' }; private readonly static byte [] endmark = new byte [] { ( byte ) '#' }; public myreceivefilter() : base (beginmark, endmark) //传入开始标记和结束标记 { } protected override stringrequestinfo processmatchedrequest( byte [] readbuffer, int offset, int length) { //todo: 通过解析到的数据来构造请求实例,并返回 } } |
然后在你的 appserver 类中使用这个接受过滤器 (receivefilter):
1
2
3
4
5
6
7
|
public class myappserver : appserver { public myappserver() : base ( new defaultreceivefilterfactory<myreceivefilter, stringrequestinfo>()) //使用默认的接受过滤器工厂 (defaultreceivefilterfactory) { } } |
5、fixedheaderreceivefilter 头部格式固定并包含内容长度协议
这种协议将一个请求定义为两大部分, 第一部分定义了包含第二部分长度等等基础信息. 我们通常称第一部分为头部.
例如, 我们有一个这样的协议: 头部包含 6 个字节, 前 4 个字节用于存储请求的名字, 后两个字节用于代表请求体的长度:
/// +-------+---+-------------------------------+
/// |request| l | |
/// | name | e | request body |
/// | (4) | n | |
/// | |(2)| |
/// +-------+---+-------------------------------+
使用 supersocket, 你可以非常方便的实现这种协议:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class myreceivefilter : fixedheaderreceivefilter<binaryrequestinfo> { public myreceivefilter() : base (6) { } protected override int getbodylengthfromheader( byte [] header, int offset, int length) { return ( int )header[offset + 4] * 256 + ( int )header[offset + 5]; } protected override binaryrequestinfo resolverequestinfo(arraysegment< byte > header, byte [] bodybuffer, int offset, int length) { return new binaryrequestinfo(encoding.utf8.getstring(header.array, header.offset, 4), bodybuffer.clonerange(offset, length)); } } |
你需要基于类fixedheaderreceivefilter实现你自己的接收过滤器.
- 传入父类构造函数的 6 表示头部的长度;
- 方法"getbodylengthfromheader(...)" 应该根据接收到的头部返回请求体的长度;
- 方法 resolverequestinfo(....)" 应该根据你接收到的请求头部和请求体返回你的请求类型的实例.
实际使用场景:
到这里五种协议的模板你都已经了解了一遍,并且知道了相关的格式处理。接下来看一个网络示例:
通讯协议格式:
在看到上图协议是在纠结客户端发送16进制,服务器怎么接收,16进制的报文如下:
26 01 00 19 4e 4a 30 31 31 01 44 41 31 31 32 00 07 00 00 00 00 00 00 34 23
16进制也好,10进制也好,其他的进制也好,最终都是转换成byte[],其实在处理数据时,发送过去的数据都是可以转换成为byte[]的,所以服务的只要解析byte[]数组就行了。按照协议来解析就能得到想要的数据。下面使用fixedsizereceivefilter的例子,代码如下:
根据上面的通讯协议,开始来实现解析:
第一步、定义一个和协议合适的数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; /**************************************************************** * 作者:黄昏前黎明后 * clr版本:4.0.30319.42000 * 创建时间:2017-01-23 21:12:30 * 2017 * 描述说明:协议数据包 * * 修改历史: * * *****************************************************************/ namespace supersocketdemo { public class hldata { /// <summary> /// 开始符号 /// </summary> public char head { get ; set ; } /// <summary> /// 协议包数据 /// </summary> public byte ping { get ; set ; } /// <summary> /// 数据长度 /// </summary> public ushort lenght { get ; set ; } /// <summary> /// 终端id /// </summary> public uint fid { get ; set ; } /// <summary> /// 目标类型 /// </summary> public byte type { get ; set ; } /// <summary> /// 转发终端id /// </summary> public uint sid { get ; set ; } /// <summary> /// 发送计数 /// </summary> public ushort sendcount { get ; set ; } /// <summary> /// 保留字段 /// </summary> public byte [] retain { get ; set ; } /// <summary> /// 异或校验 /// </summary> public byte check { get ; set ; } /// <summary> /// 结束符号 /// </summary> public char end { get ; set ; } public override string tostring() { return string .format( "开始符号:{0},包数据:{1},数据长度:{2},终端id:{3},目标类型:{4},转发终端id:{5},发送包计数:{6},保留字段:{7},异或校验:{8},结束符号:{9}" , head, ping, lenght, fid, type, sid, sendcount, retain, check, end); } } } hldata |
第二步、建立一个requestinfo来给server数据接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; using supersocket.socketbase.protocol; /**************************************************************** * 作者:黄昏前黎明后 * clr版本:4.0.30319.42000 * 创建时间:2017-01-22 21:03:31 * 2017 * 描述说明:数据请求 * * 修改历史: * * *****************************************************************/ namespace supersocketdemo { public class hlprotocolrequestinfo : requestinfo<hldata> { public hlprotocolrequestinfo(hldata hldata) { //如果需要使用命令行协议的话,那么命令类名称hldata相同 initialize( "hldata" , hldata); } } } hlprotocolrequestinfo 类 |
第三步、fixedsize协议解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; using supersocket.socketbase.protocol; using supersocket.facility.protocol; using supersocket.common; /**************************************************************** * 作者:黄昏前黎明后 * clr版本:4.0.30319.42000 * 创建时间:2017-01-22 21:06:01 * 2017 * 描述说明:协议解析类,固定请求大小的协议 * * 修改历史: * * *****************************************************************/ namespace supersocketdemo { /// <summary> /// 固定请求大小的协议,(帧格式为hlprotocolrequestinfo) /// </summary> public class hlprotocolreceivefilter : fixedsizereceivefilter<hlprotocolrequestinfo> { public hlprotocolreceivefilter() : base (25) //总的字节长度 1+1+2+5+1+5+2+6+1+1 = 25 { } protected override hlprotocolrequestinfo processmatchedrequest( byte [] buffer, int offset, int length, bool tobecopied) { var hldata = new hldata(); hldata.head = ( char )buffer[offset]; //开始标识的解析,1个字节 hldata.ping = buffer[offset + 1]; //数据,从第2位起,只有1个字节 hldata.lenght = bitconverter.touint16(buffer, offset + 2); //数据长度,从第3位开始,2个字节 hldata.fid = bitconverter.touint32(buffer, offset + 4); //本终端id,从第5位开始,5个字节 hldata.type = buffer[offset + 9]; //目标类型,从第10位开始,1个字节 hldata.sid = bitconverter.touint32(buffer, offset + 10); //转发终端id,从第11位开始,5个字节 hldata.sendcount = bitconverter.touint16(buffer, offset + 15); //发送包计数,从第16位开始,2个字节 hldata.retain = buffer.clonerange(offset + 17, 6); //保留字段,从18位开始,6个字节 hldata.check = buffer[offset + 23]; //异或校验,从24位开始,1个字节 hldata.end = ( char )buffer[offset + 24]; //结束符号,从第25位开始,一个字节 return new hlprotocolrequestinfo(hldata); } } } hlprotocolreceivefilter类 |
第四步、建立协议工厂hlreceivefilterfactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; using supersocket.socketbase; using supersocket.socketbase.protocol; using system.net; /**************************************************************** * 作者:黄昏前黎明后 * clr版本:4.0.30319.42000 * 创建时间:2017-01-23 :22:01:25 * 2017 * 描述说明:协议工厂 * * 修改历史: * * *****************************************************************/ namespace supersocketdemo { public class hlreceivefilterfactory: ireceivefilterfactory<hlprotocolrequestinfo> { public ireceivefilter<hlprotocolrequestinfo> createfilter(iappserver appserver, iappsession appsession, ipendpoint remoteendpoint) { return new hlbeginendmarkreceivefilter(); } } } hlreceivefilterfactory类 |
第五步、自定义hlprotocolsession继承appsession
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
using supersocket.socketbase; using supersocket.socketbase.protocol; using system; /**************************************************************** * 作者:黄昏前黎明后 * clr版本:4.0.30319.42000 * 创建时间:2017-01-22 21:15:11 * 2017 * 描述说明:自定义hlprotocolsession * * 修改历史: * * *****************************************************************/ namespace supersocketdemo { public class hlprotocolsession : appsession<hlprotocolsession, hlprotocolrequestinfo> { protected override void handleexception(exception e) { } } } hlprotocolsession类 |
第六步、自定义hlprotocolserver继承appserver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; using supersocket.socketbase; using supersocket.socketbase.protocol; /**************************************************************** * 作者:黄昏前黎明后 * clr版本:4.0.30319.42000 * 创建时间:2017-01-22 21:16:57 * 2017 * 描述说明:自定义server * * 修改历史: * * *****************************************************************/ namespace supersocketdemo { public class hlprotocolserver : appserver<hlprotocolsession, hlprotocolrequestinfo> { /// <summary> /// 使用自定义协议工厂 /// </summary> public hlprotocolserver() : base ( new hlreceivefilterfactory()) { } } } hlprotocolserver类 |
第七步、加上起止符协议hlbeginendmarkreceivefilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; using supersocket.common; using supersocket.facility.protocol; /**************************************************************** * 作者:黄昏前黎明后 * clr版本:4.0.30319.42000 * 创建时间:2017-01-23 22:07:03 * 2017 * 描述说明:带起止符的协议, "&" 是开始标记, "#" 是结束标记,开始结束标记由自己定义 * * 修改历史: * * *****************************************************************/ namespace supersocketdemo { public class hlbeginendmarkreceivefilter : beginendmarkreceivefilter<hlprotocolrequestinfo> { private readonly static char strbegin = '&' ; private readonly static char strend = '#' ; //开始和结束标记也可以是两个或两个以上的字节 private readonly static byte [] beginmark = new byte [] { ( byte )strbegin }; private readonly static byte [] endmark = new byte [] { ( byte )strend }; public hlbeginendmarkreceivefilter() : base (beginmark, endmark) { } /// <summary> /// 这里解析的到的数据是会把头和尾部都给去掉的 /// </summary> /// <param name="readbuffer"></param> /// <param name="offset"></param> /// <param name="length"></param> /// <returns></returns> protected override hlprotocolrequestinfo processmatchedrequest( byte [] readbuffer, int offset, int length) { var hldata = new hldata(); hldata.head = strbegin; //自己定义开始符号 hldata.ping = readbuffer[offset]; //数据,从第1位起,只有1个字节 hldata.lenght = bitconverter.touint16(readbuffer, offset + 1); //数据长度,从第2位开始,2个字节 hldata.fid = bitconverter.touint32(readbuffer, offset + 3); //本终端id,从第4位开始,5个字节 hldata.type = readbuffer[offset + 8]; //目标类型,从第9位开始,1个字节 hldata.sid = bitconverter.touint32(readbuffer, offset + 9); //转发终端id,从第10位开始,5个字节 hldata.sendcount = bitconverter.touint16(readbuffer, offset + 14); //发送包计数,从第15位开始,2个字节 hldata.retain = readbuffer.clonerange(offset + 16, 6); //保留字段,从17位开始,6个字节 hldata.check = readbuffer[offset + 22]; //异或校验,从23位开始,1个字节 hldata.end = strend; //结束符号,自己定义 return new hlprotocolrequestinfo(hldata); } } } hlbeginendmarkreceivefilter类 |
第八步、服务启动和停止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; using supersocket.socketbase; using supersocket.socketbase.protocol; using supersocket.socketengine; /**************************************************************** * 作者:黄昏前黎明后 * clr版本:4.0.30319.42000 * 创建时间:2017-01-19 00:02:17 * 2017 * 描述说明:服务启动和停止入口 * * 修改历史: 2017 -01-19 调整自定义mysession和myserver * 2017 -01-23 通讯协议解析,直接使用入口注册事件 * *****************************************************************/ namespace supersocketdemo { class program { /// <summary> /// supersocket服务启动或停止 /// </summary> /// <param name="args"></param> static void main( string [] args) { console.writeline( "请按任何键进行启动supersocket服务!" ); console.readkey(); console.writeline(); var hlprotocolserver = new hlprotocolserver(); // 设置端口号 int port = 2017; //启动应用服务端口 if (!hlprotocolserver.setup(port)) //启动时监听端口2017 { console.writeline( "服务端口启动失败!" ); console.readkey(); return ; } console.writeline(); //注册连接事件 hlprotocolserver.newsessionconnected += hlprotocolserver_newsessionconnected; //注册请求事件 hlprotocolserver.newrequestreceived += hlprotocolserver_newrequestreceived; //注册session关闭事件 hlprotocolserver.sessionclosed += hlprotocolserver_sessionclosed; //尝试启动应用服务 if (!hlprotocolserver.start()) { console.writeline( "服务启动失败!" ); console.readkey(); return ; } console.writeline( "服务器状态:" + hlprotocolserver.state.tostring()); console.writeline( "服务启动成功,请按'e'停止服务!" ); while (console.readkey().keychar != 'e' ) { console.writeline(); continue ; } //停止服务 hlprotocolserver.stop(); console.writeline( "服务已停止!" ); console.readkey(); } static void hlprotocolserver_sessionclosed(hlprotocolsession session, supersocket.socketbase.closereason value) { console.writeline(session.remoteendpoint.tostring() + "连接断开. 断开原因:" + value); } static void hlprotocolserver_newsessionconnected(hlprotocolsession session) { console.writeline(session.remoteendpoint.tostring() + " 已连接." ); } /// <summary> /// 协议并没有什么太多复杂逻辑,不需要用到命令模式,直接用这种方式就可以了 /// </summary> /// <param name="session"></param> /// <param name="requestinfo"></param> private static void hlprotocolserver_newrequestreceived(hlprotocolsession session, hlprotocolrequestinfo requestinfo) { console.writeline(); console.writeline( "数据来源: " + session.remoteendpoint.tostring()); console.writeline( "接收数据内容:" +requestinfo.body); } } } program类 |
通讯协议需要使用小工具进行调试,本人使用的是tcp/udp端口调试工具sockettool v2.大家可以直接进行下载。使用hex模式进行发送16进制报文,服务器输出结果:
本文参考官方文档 内置的常用协议实现模版
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持服务器之家!
原文链接:http://www.cnblogs.com/fly-bird/p/6345084.html