基于C#的通信协议封包(附代码)

接上一篇《基于.NET技术的监控系统应用分析》中所描述的数据通信协议设计,我们来看一下在C#中是怎么对自定义协议进行封包的?我们知道基于流的数据协议的特点:发送和接收到的数据都是连续的流。每次网络I/O操作的流长度不确定,也就是无法知道每次接收到的数据是一个完整的数据包。同样,主机发送一个数据包也会根据网络的实际情况执行若干次。所以我们对这类消息的编解码过程需要进行一个统一的封装。

重新回顾一下每个消息的结构:消息头 + 消息体。每次先发送出去的是消息头,然后是消息体。消息头里描述了这个数据包的类型,长度,序列号等信息。消息头的长度是固定的,消息体的长度是根据每个消息类型会有所的区别。

 

消息头的定义:

字段

长度(字节)

类型

说明

Length

4

Int

消息的总长度(字节)

Command ID

4

Int

命令ID

NodeID

4

Int

结点ID

TimeID

4

Int

时间戳

SequenceID

4

Int

递增序列号

 

对应的封装代码:

Head
  1using System;
  2using MonitorLib.Utility;
  3 
  4namespace 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

 

Sequence
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
        
{
            
getreturn this.node; }
            
setthis.node = value ;}
        }


        
public uint Time
        
{
            
getreturn this.time ; }
            
setthis.time = value; }

        }


        
public uint Value
        
{
            
getreturn 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位,不足用空格填充)

 

对应的封装代码:


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

    }

}

 

Command&Utility
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; }
        }
    }   
}

}

 

Bind
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 协议的封包代码:

 

ICMP
 1    public class ICMPHDR 
 2    {  
 3        private byte mType; 
 4        public byte Type 
 5        {  
 6            getreturn mType; } 
 7            set{ mType = value; } 
 8        }

 9
10        private byte mCode = 0
11        public byte Code 
12        {  
13            getreturn mCode; } 
14            set{ mCode = value; } 
15        }

16
17        private ushort mChecksum = 0
18        public ushort Checksum 
19        {  
20            getreturn mChecksum; } 
21            set{ mChecksum = value; } 
22        }

23
24        private ushort mID; 
25        public ushort ID 
26        {  
27            getreturn mID; } 
28            set{ mID = value; } 
29        }

30        
31        private ushort mSeq;
32        public ushort Seq 
33        {  
34            getreturn mSeq; } 
35            set{ mSeq = value; } 
36        }

37 
38        private ulong mtmSend;
39        public ulong tmSend 
40        {  
41            getreturn mtmSend; } 
42            set{ mtmSend = value; } 
43        }
 
44
45        private int mnTaskId; 
46        public int nTaskId 
47        {  
48            getreturn 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#是如何对自定义的通信协议封装的过程。 如有不同的处理方法的朋友,欢迎评论,一起探讨一下。

 

你可能感兴趣的:(基于C#的通信协议封包(附代码))