我们知道基于流的数据协议的特点:发送和接收到的数据都是连续的流。每次网络I/O操作的流长度不确定,也就是无法知道每次接收到的数据是一个完整的数据包。同样,主机发送一个数据包也会根据网络的实际情况执行若干次。所以我们对这类消息的编解码过程需要进行一个统一的封装。
重新回顾一下每个消息的结构:消息头 + 消息体。每次先发送出去的是消息头,然后是消息体。消息头里描述了这个数据包的类型,长度,序列号等信息。消息头的长度是固定的,消息体的长度是根据每个消息类型会有所的区别。
消息头的定义:
字段 |
长度(字节) |
类型 |
说明 |
Length |
4 |
Int |
消息的总长度(字节) |
Command ID |
4 |
Int |
命令ID |
NodeID |
4 |
Int |
结点ID |
TimeID |
4 |
Int |
时间戳 |
SequenceID |
4 |
Int |
递增序列号 |
对应的封装代码:


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



92

93

94

95

96

97


98

99

100

101



102

103



104

105

106

107



108

109

110



111

112

113

114

115

116


117

118

119

120



121

122



123

124

125

126



127

128

129



130

131

132

133

134

135

136


137

138

139

140

141



142

143

144

145


146

147

148

149

150



151

152



153

154

155

156

157


158

159

160

161



162

163



164

165

166

167

168

169












































































































上面只是一个消息头,要成为一个完整的消息,一般还必须包含消息体(当然你也可以根据需要仅发送一个消息头的数据,作为特殊用途,例如自定义的心跳包)。举个例子:客户机与服务器连接上后,它通常会发送一个绑定(Bind) 消息给服务器端。例如:验证确认客户端的合法性。那么此时的Bind消息的格式是:
字段 |
长度(字节) |
类型 |
说明 |
HEAD |
|
|
上面的消息头部 |
loginName |
16 |
string |
用户名(固定16位,不足用空格填充) |
LoginPassword |
16 |
string |
密码(固定16位,不足用空格填充) |
对应的封装代码:








































using System;
using System.IO;
namespace MonitorLib.Utility
{
/// <summary>
///消息命令常量
/// </summary>
public enum Command : uint
{
/// <summary>
/// 对客户端验证
/// </summary>
MOT_BIND = 0x1,
/// <summary>
/// 服务端返回验证请求
/// </summary>
MOT_BIND_RESP = 0x80000001,
/// <summary>
/// 断开连接
/// </summary>
MOT_UNBIND =0x2,
/// <summary>
/// 返回断开连接状态
/// </summary>
MOT_UNBIND_RESP=0x80000002,
/// <summary>
/// 上行提交内容
/// </summary>
MOT_SUBMIT = 0x3,
/// <summary>
/// submit 应答
/// </summary>
MOT_SUBMIT_RESP = 0x80000003,
/// <summary>
/// 设置命令
/// </summary>
MOT_REQUEST = 0x4,
MOT_REQUEST_RESP = 0x80000004,
/// <summary>
/// 连接命令
/// </summary>
MOT_CONNECT = 0x5,
MOT_CONNECT_RESP = 0x80000005,
/// <summary>
/// 更新程序命令
/// </summary>
MOT_UPDATE = 0x6,
MOT_UPDATE_RESP = 0x80000006,
/// <summary>
/// 返回结点的数据参数
/// </summary>
MOT_RESPONSE_PARAM = 0x7,
MOT_CLIENTINFO = 0x8,
MOT_CLIENTINFO_RESP = 0x80000008
}
/// <summary>
/// 错误定义
/// </summary>
public enum ErrorDefine : int
{
/// <summary>
///无错误,命令正确接收
/// </summary>
NO_ERROR = 0,
/// <summary>
/// 非法登录,如登录名、口令出错、登录名与口令不符等
/// </summary>
ILLEAGE_LOGIN = 1,
/// <summary>
/// 重复登录,如在同一TCP/IP连接中连续两次以上请求登录。
/// </summary>
REPEAT_LOGIN =2,
/// <summary>
/// 连接过多,指单个节点要求同时建立的连接数过多。
/// </summary>
MORE_CONNECT = 3,
/// <summary>
/// 不知道的用户
/// </summary>
UNKNOW_USER = 29,
/// <summary>
/// 不提供此功能
/// </summary>
UNSUPPORT_FUNCTION = 30,
/// <summary>
/// 系统失败
/// </summary>
SYSTEM_FAIL = 32,
}
/// <summary>
/// 字节 整形 转换类 网络格式转换为内存格式
/// </summary>
public class Converter
{
/// <summary>
/// 转换整形数据网络次序的字节数组
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public static byte[] IntToBytes(uint i)
{
byte[] t = BitConverter.GetBytes(i) ;
byte b = t[0];
t[0] = t[3];
t[3] = b;
b = t[1];
t[1] = t[2];
t[2] = b;
return (t);
}
public static byte[] IntToBytes(uint source,int number)
{
byte[] t = new byte[number];
t = BitConverter.GetBytes(source);
byte temp;
for (int i = t.Length-1; i > t.Length/2; i--)
{
temp = t[i];
t[i] = t[t.Length-1-i];
t[t.Length-1-i] = temp;
}
return (t);
}
/// <summary>
/// 返回字节数组代表的整数数字,4个数组
/// </summary>
/// <param name="bs"></param>
/// <param name="startIndex"></param>
/// <returns></returns>
public static uint BytesToUInt(byte[] bs,int startIndex)
{
byte[] t=new byte[4];
for (int i = 0; i < 4 && i < bs.Length-startIndex; i++)
{
t[i]=bs[startIndex+i];
}
byte b=t[0];
t[0]=t[3];
t[3]=b;
b=t[1];
t[1]=t[2];
t[2]=b;
return BitConverter.ToUInt32(t,0);
}
public static uint BytesToUInt(byte[] b,int startIndex,int number)
{
byte[] t = new Byte[number];
for (int i = 0; i < number && i < b.Length-startIndex; i++)
{
t[i] = b[startIndex+i];
}
byte temp;
for (int i = t.Length-1; i > t.Length/2; i--)
{
temp = t[i];
t[i]=t[t.Length-1-i];
t[i] = temp;
}
return (BitConverter.ToUInt32(t,0));
}
/// <summary>
/// 没有指定起始索引
/// </summary>
/// <param name="bs"></param>
/// <returns></returns>
public static uint BytesToUInt(byte[] bs)
{
return (BytesToUInt(bs,0));
}
}
/// <summary>
/// 缓冲区对象
/// </summary>
public class BufferObject
{
private byte[] buffer = null;
private int length = 0;
public BufferObject(byte[] bytes, int len)
{
if (buffer != null)
{
buffer = null;
GC.Collect();
}
length = len;
buffer = new byte[len];
for (int i = 0; i < len; i++)
{
buffer[i] = bytes[i];
}
}
public byte[] Buffer
{
get { return buffer;}
}
public int Length
{
get { return length; }
}
}
}
}


























































































































































































除了这种协议封装方法外,还有一种直接利用 .NET 的字节流操作类来编解码,例如 ICMP 协议的封包代码:


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

以上介绍了用C#是如何对自定义的通信协议封装的过程。 如有不同的处理方法的朋友,欢迎评论,一起探讨一下。