• C#网络编程数据传输中封装数据帧头的方法


      在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 }
  • 相关阅读:
    图解SQL的Join(转)
    MySQ数据表设计
    关于数据库DML、DDL、DCL区别
    SQL多表连接查询
    Xcode报错Expected selector for Objective-C and Expected method body
    Mac上安装使用MYSQL以及Navicat数据库管理和PHP服务器配置
    更换app开发者账号
    Mac 下的 C++ 开发环境
    spring-retry 重试机制
    Ribbon的主要组件与工作流程
  • 原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/6149539.html
Copyright © 2020-2023  润新知