http://www.cnblogs.com/wildman/archive/2009/07/06/1517629.html
接上一篇《基于.NET技术的监控系统应用分析》中所描述的数据通信协议设计,我们来看一下在C#中是怎么对自定义协议进行封包的?我们知道基于流的数据协议的特点:发送和接收到的数据都是连续的流。每次网络I/O操作的流长度不确定,也就是无法知道每次接收到的数据是一个完整的数据包。同样,主机发送一个数据包也会根据网络的实际情况执行若干次。所以我们对这类消息的编解码过程需要进行一个统一的封装。
重新回顾一下每个消息的结构:消息头 + 消息体。每次先发送出去的是消息头,然后是消息体。消息头里描述了这个数据包的类型,长度,序列号等信息。消息头的长度是固定的,消息体的长度是根据每个消息类型会有所的区别。
消息头的定义:
字段
|
长度(字节)
|
类型
|
说明
|
Length
|
4
|
Int
|
消息的总长度(字节)
|
Command ID
|
4
|
Int
|
命令ID
|
NodeID
|
4
|
Int
|
结点ID
|
TimeID
|
4
|
Int
|
时间戳
|
SequenceID
|
4
|
Int
|
递增序列号
|
对应的封装代码:

1
using System;
2
using MonitorLib.Utility;
3
4
namespace MonitorLib.Protocol
5
{
6
/// <summary>
7
/// 消息头
8
/// </summary>
9
[Serializable]
10
public class Head
11
{
12
private byte[] initValue = new byte[Head.HeaderLength];
13
14
public Head(Command CommandID)
15
{
16
Converter.IntToBytes((uint)CommandID).CopyTo(initValue, 4);
17
}
18
19
public Head(byte[] bs)
20
{
21
uint length = Head.HeaderLength ;
22
23
for (int i = 0;i < length;i++)
24
{
25
initValue[i]=bs[i];
26
}
27
}
28
29
30
public Head(byte[] bs,int baseIndex)
31
{
32
uint length = Head.HeaderLength ;
33
34
for (int i = 0; i < length; i++)
35
{
36
initValue[i]=bs[baseIndex+i];
37
}
38
}
39
40
/// <summary>
41
/// 消息的整个长度
42
/// </summary>
43
public uint Length
44
{
45
get
46
{
47
return (Converter.BytesToUInt(initValue,0));
48
}
49
set
50
{
51
byte[] byt = Converter.IntToBytes(value);
52
for (int i = 0;i < 4;i++)
53
{
54
initValue[i]= byt[i];
55
}
56
}
57
}
58
59
/// <summary>
60
/// 命令类型
61
/// </summary>
62
public uint CommandID
63
{
64
get
65
{
66
return (Converter.BytesToUInt(initValue, 4));
67
}
68
set
69
{
70
byte[] t=Converter.IntToBytes(value);
71
for (int i = 0; i < 4; i++)
72
{
73
initValue[i + 4] = t[i];
74
}
75
}
76
}
77
78
/// <summary>
79
/// 源结点号
80
/// </summary>
81
public uint NodeID
82
{
83
get
84
{
85
return (Converter.BytesToUInt(initValue, 8));
86
}
87
set
88
{
89
byte[] t = Converter.IntToBytes(value);
90
for (int i = 0; i < 4; i++)
91
{
92
initValue[i + 8] = t[i];
93
}
94
}
95
}
96
97
/// <summary>
98
/// 时间戳
99
/// </summary>
100
public uint TimeID
101
{
102
get
103
{
104
return (Converter.BytesToUInt(initValue,12));
105
}
106
set
107
{
108
byte[] t = Converter.IntToBytes(value);
109
for (int i = 0; i < 4; i++)
110
{
111
initValue[i + 12] = t[i];
112
}
113
}
114
}
115
116
/// <summary>
117
/// 序列号
118
/// </summary>
119
public uint SequenceID
120
{
121
get
122
{
123
return (Converter.BytesToUInt(initValue,16));
124
}
125
set
126
{
127
byte[] t = Converter.IntToBytes(value);
128
for (int i = 0;i < 4;i++)
129
{
130
initValue[i + 16] = t[i];
131
}
132
}
133
}
134
135
136
/// <summary>
137
/// 输出字节流
138
/// </summary>
139
/// <returns></returns>
140
public byte[] ToBytes()
141
{
142
return initValue;
143
}
144
145
/// <summary>
146
/// 从字节流中转换
147
/// </summary>
148
/// <param name="bs"></param>
149
public void FromBytes(byte[] bs)
150
{
151
for (int i = 0; i < Head.HeaderLength; i++)
152
{
153
initValue[i] = bs[i];
154
}
155
}
156
157
/// <summary>
158
/// 消息头的长度
159
/// </summary>
160
public static uint HeaderLength
161
{
162
get
163
{
164
return (4 + 4 + 12);
165
}
166
}
167
}
168
}
169

using System;
using MonitorLib.Utility;

namespace MonitorLib.Protocol
{
/// <summary>
/// Sequence 的摘要说明。
/// </summary>
[Serializable]
public class Sequence
{
private uint node;
private uint time;
private uint sequence ;
public Sequence()
{
}

public uint Node
{
get{ return this.node; }
set{ this.node = value ;}
}

public uint Time
{
get{ return this.time ; }
set{ this.time = value; }

}

public uint Value
{
get{ return sequence;}
set{this.sequence = value;}
}

public ulong ToUInt64()
{
string temp = String.Format("{0}{1}{2}",Node, Time, Value);
return Convert.ToUInt64(temp);
}
}

public class Seed
{
private uint sequence = uint.MinValue;

public uint GetSequence()
{
lock (this)
{
return this.sequence >= 90000 ? uint.MinValue : this.sequence++;
}
}

public uint GetTimeStamp()
{
lock (this)
{
return Convert.ToUInt32( DateTime.Now.ToString("MMddHHmmss") );
}
}

}
}

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

using System;
using MonitorLib.Utility;

namespace MonitorLib.Protocol
{
/// <summary>
/// AbstractBase 的摘要说明。
/// </summary>
[Serializable]
public abstract class AbstractBase
{
protected byte[] initValue;
public Head header;

public AbstractBase()
{
}

public virtual byte[] ToBytes()
{
return null;
}
}
}


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; }
}
}
}
}

using System;
using System.Text;
using MonitorLib.Utility;

namespace MonitorLib.Protocol
{
/// <summary>
/// Bind消息
/// </summary>
[Serializable]
public class Bind : AbstractBase
{
private string loginName;
private string loginPassword;

/// <summary>
/// 初始Bind命令的消息头
/// </summary>
/// <param name="Sequence">序列号</param>
public Bind(Sequence seq)
{
header = new Head(Command.MOT_BIND);
header.NodeID = seq.Node;
header.TimeID = seq.Time ;
header.SequenceID = seq.Value ;
header.Length = Head.HeaderLength + 16 + 16;
}
public Bind(byte[] receive)
{
initValue = new byte[receive.Length];
receive.CopyTo(initValue,0);
}

/// <summary>
/// 登录名
/// </summary>
public string LoginName
{
get
{
return Encoding.ASCII.GetString(initValue,20,16);
}
set
{
loginName = value;
}
}

/// <summary>
/// 密码
/// </summary>
public string LoginPassword
{
get
{
return Encoding.ASCII.GetString(initValue,36,16);
}
set
{
loginPassword = value;
}
}

/// <summary>
/// 把消息结构转换成字节数组
/// </summary>
/// <returns>结果字节数组</returns>
public override byte[] ToBytes()
{
byte[] retValue = new byte[this.header.Length];
uint index = 0;

//填充消息头
header.ToBytes().CopyTo(retValue,index);

index += Head.HeaderLength;
Encoding.ASCII.GetBytes(loginName).CopyTo(retValue,index);

//移位16位, 填充密码
index += 16;
Encoding.ASCII.GetBytes(loginPassword).CopyTo(retValue,index);
return retValue;
}
}


/// <summary>
/// Bind应答结构
/// </summary>
[Serializable]
public class Bind_Resp : AbstractBase
{
private uint result;

/// <summary>
/// 构造函数,把接收的字节数组复制到initValue
/// </summary>
/// <param name="recBytes">从网络上接收到的字节数组</param>
public Bind_Resp(byte[] receive)
{
initValue = new byte[receive.Length];
receive.CopyTo(initValue,0);
}

public Bind_Resp(Sequence seq)
{
header = new Head(Command.MOT_BIND_RESP);
header.NodeID = seq.Node;
header.TimeID = seq.Time ;
header.SequenceID = seq.Value ;
header.Length = Head.HeaderLength + 4;
}

/// <summary>
/// bind 执行命令是否成功,0-成功。其它:错误码。
/// </summary>
public uint Result
{
get
{
return Convert.ToUInt32(initValue[20].ToString());
}
set
{
result = value;
}
}

public override byte[] ToBytes()
{
byte[] retValue = new byte[header.Length];
header.ToBytes().CopyTo(retValue,0);
BitConverter.GetBytes(result).CopyTo(retValue,20);
return retValue;
}
}

}

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

1
public class ICMPHDR
2
{
3
private byte mType;
4
public byte Type
5
{
6
get{ return mType; }
7
set{ mType = value; }
8
}
9
10
private byte mCode = 0;
11
public byte Code
12
{
13
get{ return mCode; }
14
set{ mCode = value; }
15
}
16
17
private ushort mChecksum = 0;
18
public ushort Checksum
19
{
20
get{ return mChecksum; }
21
set{ mChecksum = value; }
22
}
23
24
private ushort mID;
25
public ushort ID
26
{
27
get{ return mID; }
28
set{ mID = value; }
29
}
30
31
private ushort mSeq;
32
public ushort Seq
33
{
34
get{ return mSeq; }
35
set{ mSeq = value; }
36
}
37
38
private ulong mtmSend;
39
public ulong tmSend
40
{
41
get{ return mtmSend; }
42
set{ mtmSend = value; }
43
}
44
45
private int mnTaskId;
46
public int nTaskId
47
{
48
get{ return mnTaskId; }
49
set{ mnTaskId = value; }
50
}
51
52
public void Encode(BinaryWriter writer)
53
{
54
writer.Write(Type);
55
writer.Write(Code);
56
writer.Write((UInt16)Checksum);
57
writer.Write((UInt16)ID);
58
writer.Write((UInt16)Seq);
59
writer.Write((UInt32)tmSend);
60
writer.Write(nTaskId);
61
}
62
63
public void Decode(BinaryReader reader)
64
{
65
Type = reader.ReadByte();
66
Code = reader.ReadByte();
67
Checksum = reader.ReadUInt16();
68
ID = reader.ReadUInt16();
69
Seq = reader.ReadUInt16();
70
tmSend = reader.ReadUInt32();
71
nTaskId = reader.ReadInt32();
72
}
73
74
public uint Sum()
75
{
76
uint sum = 0;
77
sum += (ushort)(Type + (Code << 8));
78
sum += (ushort)ID;
79
sum += (ushort)Seq;
80
sum += (ushort)tmSend;
81
sum += (ushort)(tmSend >> 16);
82
sum += (ushort)nTaskId;
83
sum += (ushort)(nTaskId >> 16);
84
return sum;
85
}
86
}
以上介绍了用C#是如何对自定义的通信协议封装的过程。 如有不同的处理方法的朋友,欢迎评论,一起探讨一下。