首页 > Linux操作系统 > Linux操作系统 > 基于C#的通信协议封包(附代码)
接上一篇《基于.NET技术的监控系统应用分析》中所描述的数据通信协议设计,我们来看一下在C#中是怎么对自定义协议进行封包的?我们知道基于流的数据协议的特点:发送和接收到的数据都是连续的流。每次网络I/O操作的流长度不确定,也就是无法知道每次接收到的数据是一个完整的数据包。同样,主机发送一个数据包也会根据网络的实际情况执行若干次。所以我们对这类消息的编解码过程需要进行一个统一的封装。
重新回顾一下每个消息的结构:消息头 + 消息体。每次先发送出去的是消息头,然后是消息体。消息头里描述了这个数据包的类型,长度,序列号等信息。消息头的长度是固定的,消息体的长度是根据每个消息类型会有所的区别。
消息头的定义:
字段 | 长度(字节) | 类型 | 说明 |
Length | 4 | Int | 消息的总长度(字节) |
Command ID | 4 | Int | 命令ID |
NodeID | 4 | Int | 结点ID |
TimeID | 4 | Int | 时间戳 |
SequenceID | 4 | Int | 递增序列号 |
对应的封装代码:
上面只是一个消息头,要成为一个完整的消息,一般还必须包含消息体(当然你也可以根据需要仅发送一个消息头的数据,作为特殊用途,例如自定义的心跳包)。举个例子:客户机与服务器连接上后,它通常会发送一个绑定(Bind) 消息给服务器端。例如:验证确认客户端的合法性。那么此时的Bind消息的格式是:
字段 | 长度(字节) | 类型 | 说明 |
HEAD |
|
| 上面的消息头部 |
loginName | 16 | string | 用户名(固定16位,不足用空格填充) |
LoginPassword | 16 | string | 密码(固定16位,不足用空格填充) |
对应的封装代码:
using System;
using MonitorLib.Utility;
namespace MonitorLib.Protocol
{
///
/// AbstractBase 的摘要说明。
///
[Serializable]
public abstract class AbstractBase
{
protected byte[] initValue;
public Head header;
public AbstractBase()
{
}
public virtual byte[] ToBytes()
{
return null;
}
}
}
using System;
using System.Text;
using MonitorLib.Utility;
namespace MonitorLib.Protocol
{
///
/// Bind消息
///
[Serializable]
public class Bind : AbstractBase
{
private string loginName;
private string loginPassword;
///
/// 初始Bind命令的消息头
///
/// 序列号
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);
}
///
/// 登录名
///
public string LoginName
{
get
{
return Encoding.ASCII.GetString(initValue,20,16);
}
set
{
loginName = value;
}
}
///
/// 密码
///
public string LoginPassword
{
get
{
return Encoding.ASCII.GetString(initValue,36,16);
}
set
{
loginPassword = value;
}
}
///
/// 把消息结构转换成字节数组
///
///
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;
}
}

///
/// Bind应答结构
///
[Serializable]
public class Bind_Resp : AbstractBase
{
private uint result;
///
/// 构造函数,把接收的字节数组复制到initValue
///
/// 从网络上接收到的字节数组
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;
}
///
/// bind 执行命令是否成功,0-成功。其它:错误码。
///
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 协议的封包代码:
public class ICMPHDR
{
private byte mType;
public byte Type
{
get{ return mType; }
set{ mType = value; }
}
private byte mCode = 0;
public byte Code
{
get{ return mCode; }
set{ mCode = value; }
}
private ushort mChecksum = 0;
public ushort Checksum
{
get{ return mChecksum; }
set{ mChecksum = value; }
}
private ushort mID;
public ushort ID
{
get{ return mID; }
set{ mID = value; }
}
private ushort mSeq;
public ushort Seq
{
get{ return mSeq; }
set{ mSeq = value; }
}
private ulong mtmSend;
public ulong tmSend
{
get{ return mtmSend; }
set{ mtmSend = value; }
} 
private int mnTaskId;
public int nTaskId
{
get{ return mnTaskId; }
set{ mnTaskId = value; }
}
public void Encode(BinaryWriter writer)
{
writer.Write(Type);
writer.Write(Code);
writer.Write((UInt16)Checksum);
writer.Write((UInt16)ID);
writer.Write((UInt16)Seq);
writer.Write((UInt32)tmSend);
writer.Write(nTaskId);
} 
public void Decode(BinaryReader reader)
{
Type = reader.ReadByte();
Code = reader.ReadByte();
Checksum = reader.ReadUInt16();
ID = reader.ReadUInt16();
Seq = reader.ReadUInt16();
tmSend = reader.ReadUInt32();
nTaskId = reader.ReadInt32();
} 
public uint Sum()
{
uint sum = 0;
sum += (ushort)(Type + (Code << 8));
sum += (ushort)ID;
sum += (ushort)Seq;
sum += (ushort)tmSend;
sum += (ushort)(tmSend >> 16);
sum += (ushort)nTaskId;
sum += (ushort)(nTaskId >> 16);
return sum;
}
}
以上介绍了用C#是如何对自定义的通信协议封装的过程。 如有不同的处理方法的朋友,欢迎评论,一起探讨一下。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12639172/viewspace-608424/ ,如需转载,请注明出处,否则将追究法律责任。