在C/S端编程的时候,经常要在C端和S端之间传数据时自定义一下报文的帧头,如果是在C/C++,封装帧头是一件很简单的事情,直接把unsigned char *强转为struct就行,但是在C#中,并没有提供直接从struct到byte[]的转换,这个时候就需要用到Marshal等非托管的方法了。
自定义帧
我们可以在C#中写出如下代码:
1 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1,Size = 12)]
2 [Serializable()]
3 public struct DatagramHeaderFrame
4 {
5 // MessageType类型:
6 public MessageType MsgType;
7
8 //一个四个字节的特征码
9 public uint FeatureCode;
10
11 //用于标识报文的长度,用于校验
12 public int MessageLength;
13 }
首先我们说明一下,StructLayout是一个用于管理struct的布局特性,
CharSet指示在默认情况下是否应将类中的字符串数据字段作为 LPWSTR 或 LPSTR 进行封送处理;
Pack控制类或结构的数据字段在内存中的对齐方式。
Size指示类或结构的绝对大小。
LayoutKind是布局的类型,这个枚举有三个值:
Auto,运行库自动为非托管内存中的对象的成员选择适当的布局。 使用此枚举成员定义的对象不能在托管代码的外部公开。 尝试这样做将引发异常。
Explicit,在未管理内存中的每一个对象成员的精确位置是被显式控制的,服从于 StructLayoutAttribute. Pack 字段的设置。每个成员必须使用 FieldOffsetAttribute 指示该字段在类型中的位置。在MSDN文档中为我们展示了下面的一个例子:
1 [StructLayout(LayoutKind.Explicit)]
2 public struct Rect
3 {
4 [FieldOffset(0)] public int left;
5 [FieldOffset(4)] public int top;
6 [FieldOffset(8)] public int right;
7 [FieldOffset(12)] public int bottom;
8 }
Sequential,对象的成员按照它们在被导出到非托管内存时出现的顺序依次布局。 这些成员根据在 StructLayoutAttribute. Pack 中指定的封装进行布局,并且可以是不连续的。
Serialzable是一个用于指示对象是否能序列化的特性,简单的来说序列化的用处就是,比如我客户端给你传一定的数据,这个数据不是标准的类型的时候,如果我们不使用序列化的时候,我们就要把数据的每个部分都转成二进制然后存储,就很麻烦,而且容易出错,所以C#就提供了这样一个机制给程序员简单使用并且转成二进制(或者其他格式)来使用(使用formatter),而且当一个对象没有被标明为可序列化的时候,我们使用formatter的时候会报错,具体怎么使用请查看MSDN文档即可。比如文档有这样一段代码:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
//using System.Runtime.Serialization.Formatters.Binary;
public class Test {
public static void Main() {
//Creates a new TestSimpleObject object.
TestSimpleObject obj = new TestSimpleObject();
Console.WriteLine("Before serialization the object contains: ");
obj.Print();
//Opens a file and serializes the object into it in binary format.
Stream stream = File.Open("data.xml", FileMode.Create);
SoapFormatter formatter = new SoapFormatter();
//BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, obj);
stream.Close();
//Empties obj.
obj = null;
//Opens file "data.xml" and deserializes the object from it.
stream = File.Open("data.xml", FileMode.Open);
formatter = new SoapFormatter();
//formatter = new BinaryFormatter();
obj = (TestSimpleObject)formatter.Deserialize(stream);
stream.Close();
Console.WriteLine("");
Console.WriteLine("After deserialization the object contains: ");
obj.Print();
}
}
// A test object that needs to be serialized.
[Serializable()]
public class TestSimpleObject {
public int member1;
public string member2;
public string member3;
public double member4;
// A field that is not serialized.
[NonSerialized()] public string member5;
public TestSimpleObject() {
member1 = 11;
member2 = "hello";
member3 = "hello";
member4 = 3.14159265;
member5 = "hello world!";
}
public void Print() {
Console.WriteLine("member1 = '{0}'", member1);
Console.WriteLine("member2 = '{0}'", member2);
Console.WriteLine("member3 = '{0}'", member3);
Console.WriteLine("member4 = '{0}'", member4);
Console.WriteLine("member5 = '{0}'", member5);
}
}
封装和解析
要解析一个struct并且把他变成bytes,需要用到非托管的方法:
public static byte[] StructToBytes(object structObj)
{
int size = Marshal.SizeOf(structObj);
IntPtr buffer = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(structObj, buffer, false);
byte[] bytes = new byte[size];
Marshal.Copy(buffer, bytes, 0, size);
return bytes;
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
要把bytes变成sturct,反过来即可:
1 public static object BytesToStruct(byte[] bytes, Type strcutType)
2 {
3 int size = Marshal.SizeOf(strcutType);
4 IntPtr buffer = Marshal.AllocHGlobal(size);
5 try
6 {
7 Marshal.Copy(bytes, 0, buffer, size);
8 return Marshal.PtrToStructure(buffer, strcutType);
9 }
10 finally
11 {
12 Marshal.FreeHGlobal(buffer);
13 }
14 }
下面演示一下在socket上传输报文+帧头:
1 public static byte[] PackingMessageToBytes
2 (MessageType messageType, uint featureCode, int messageLength, byte[] msgBytes)
3 {
4 DatagramHeaderFrame frame = new DatagramHeaderFrame();
5 frame.MsgType = messageType;
6 frame.FeatureCode = featureCode;
7 frame.MessageLength = messageLength;
8
9 byte[] header = StructToBytes(frame);
10
11 byte[] datagram = new byte[header.Length + msgBytes.Length];
12 header.CopyTo(datagram, 0);
13 msgBytes.CopyTo(datagram, FrameSize);
14
15 return datagram;
16 }
17
18 /// <summary>
19 /// 封装消息和报文
20 /// </summary>
21 /// <param name="headerFrame">报文帧头</param>
22 /// <param name="message">报文</param>
23 /// <param name="encoding">编码器</param>
24 /// <returns></returns>
25 public static byte[] PackingMessageToBytes
26 (DatagramHeaderFrame headerFrame, byte[] msgBytes)
27 {
28 byte[] header = StructToBytes(headerFrame);
29
30 byte[] datagram = new byte[header.Length + msgBytes.Length];
31 header.CopyTo(datagram, 0);
32 msgBytes.CopyTo(datagram, FrameSize);
33
34 return datagram;
接收端代码节选:
1 DatagramHeaderFrame headerFrame = new DatagramHeaderFrame();
2 headerFrame.MsgType = messageType;
3 headerFrame.MessageLength = bytes.Length;
4 byte[] datagram = PackingMessageToBytes(headerFrame, bytes);
5
6 GetStream().BeginWrite(datagram, 0, datagram.Length, HandleDatagramWritten, this);
发送端代码节选:
1 DatagramHeaderFrame headerFrame = new DatagramHeaderFrame();
2 byte[] datagramBytes = new byte[0];
3
4 byte[] datagramBuffer = (byte[])ar.AsyncState;
5 byte[] recievedBytes = new byte[numberOfRecievedBytes];
6
7 Buffer.BlockCopy(datagramBuffer, 0, recievedBytes, 0, numberOfRecievedBytes);
8 PrasePacking(recievedBytes, numberOfRecievedBytes, ref headerFrame, ref datagramBytes);
9
10 GetStream().BeginRead(datagramBuffer, 0, datagramBuffer.Length, HandleDatagramReceived, datagramBuffer);
C++端解析和封装的代码(用Qt写的)
1 QByteArray TcpHeaderFrameHelper::bindHeaderAndDatagram(const TcpHeaderFrame &header,const QByteArray &realDataBytes)
2 {
3 QByteArray byteArray, temp;
4 temp.resize(4);
5
6 unsignedToQByteArray((unsigned)header.messageType, temp);
7 byteArray += temp;
8
9 unsignedToQByteArray((unsigned)header.featureCode, temp);
10 byteArray += temp;
11
12 unsignedToQByteArray((unsigned)header.messageLength, temp);
13 byteArray += temp;
14
15 byteArray +=realDataBytes;
16 return byteArray;
17 }
18
19 void TcpHeaderFrameHelper::praseHeaderAndDatagram(const QByteArray &dataBytes,TcpHeaderFrame &headerFrame,QByteArray &realDataBytes)
20 {
21 realDataBytes.resize(dataBytes.size() - TcpHeaderFrameHelper::headerSize);
22 headerFrame.messageType = qByteArrayToInt(dataBytes.left(4));
23 headerFrame.featureCode = qByteArrayToInt(dataBytes.mid(4,4));
24 headerFrame.messageLength = qByteArrayToInt(dataBytes.mid(8,4));
25
26 realDataBytes = dataBytes.mid(12, dataBytes.size());
27 }
28
29 unsigned TcpHeaderFrameHelper::qByteArrayToInt(QByteArray bytes)
30 {
31 int result = 0;
32 result |= ((bytes[0]) & 0x000000ff);
33 result |= ((bytes[1] << 8) & 0x0000ff00);
34 result |= ((bytes[2] << 16) & 0x00ff0000);
35 result |= ((bytes[3] << 24) & 0xff000000);
36
37 return result;
38 }
39
40 void TcpHeaderFrameHelper::unsignedToQByteArray(unsigned num, QByteArray &bytes)
41 {
42 bytes.resize(4);
43 bytes[0] = (char)( 0x000000ff & num);
44 bytes[1] = (char)((0x0000ff00 & (num)) >> 8);
45 bytes[2] = (char)((0x00ff0000 & (num)) >> 16);
46 bytes[3] = (char)((0xff000000 & (num)) >> 24);
47 }