在C/S端编程的时候,经常要在C端和S端之间传数据时自定义一下报文的帧头,如果是在C/C++,封装帧头是一件很简单的事情,直接把unsigned char *强转为struct就行,但是在C#中,并没有提供直接从struct到byte[]的转换,这个时候就需要用到Marshal等非托管的方法了。

自定义帧


我们可以在C#中写出如下代码:

 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = ,Size = )]
[Serializable()]
public struct DatagramHeaderFrame
{
// MessageType类型:
public MessageType MsgType; //一个四个字节的特征码
public uint FeatureCode; //用于标识报文的长度,用于校验
public int MessageLength;
}

  首先我们说明一下,StructLayout是一个用于管理struct的布局特性,

CharSet指示在默认情况下是否应将类中的字符串数据字段作为 LPWSTR 或 LPSTR 进行封送处理;

Pack控制类或结构的数据字段在内存中的对齐方式。

Size指示类或结构的绝对大小。

  LayoutKind是布局的类型,这个枚举有三个值:

Auto,运行库自动为非托管内存中的对象的成员选择适当的布局。 使用此枚举成员定义的对象不能在托管代码的外部公开。 尝试这样做将引发异常。

Explicit,在未管理内存中的每一个对象成员的精确位置是被显式控制的,服从于 StructLayoutAttribute. Pack 字段的设置。每个成员必须使用 FieldOffsetAttribute 指示该字段在类型中的位置。在MSDN文档中为我们展示了下面的一个例子:

 [StructLayout(LayoutKind.Explicit)]
public struct Rect
{
[FieldOffset()] public int left;
[FieldOffset()] public int top;
[FieldOffset()] public int right;
[FieldOffset()] public int bottom;
}

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 = ;
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, , size);
return bytes;
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}

要把bytes变成sturct,反过来即可:

         public static object BytesToStruct(byte[] bytes, Type strcutType)
{
int size = Marshal.SizeOf(strcutType);
IntPtr buffer = Marshal.AllocHGlobal(size);
try
{
Marshal.Copy(bytes, , buffer, size);
return Marshal.PtrToStructure(buffer, strcutType);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}

下面演示一下在socket上传输报文+帧头:

         public static byte[] PackingMessageToBytes
(MessageType messageType, uint featureCode, int messageLength, byte[] msgBytes)
{
DatagramHeaderFrame frame = new DatagramHeaderFrame();
frame.MsgType = messageType;
frame.FeatureCode = featureCode;
frame.MessageLength = messageLength; byte[] header = StructToBytes(frame); byte[] datagram = new byte[header.Length + msgBytes.Length];
header.CopyTo(datagram, );
msgBytes.CopyTo(datagram, FrameSize); return datagram;
} /// <summary>
/// 封装消息和报文
/// </summary>
/// <param name="headerFrame">报文帧头</param>
/// <param name="message">报文</param>
/// <param name="encoding">编码器</param>
/// <returns></returns>
public static byte[] PackingMessageToBytes
(DatagramHeaderFrame headerFrame, byte[] msgBytes)
{
byte[] header = StructToBytes(headerFrame); byte[] datagram = new byte[header.Length + msgBytes.Length];
header.CopyTo(datagram, );
msgBytes.CopyTo(datagram, FrameSize); return datagram;

接收端代码节选:

 DatagramHeaderFrame headerFrame = new DatagramHeaderFrame();
headerFrame.MsgType = messageType;
headerFrame.MessageLength = bytes.Length;
byte[] datagram = PackingMessageToBytes(headerFrame, bytes); GetStream().BeginWrite(datagram, , datagram.Length, HandleDatagramWritten, this);

发送端代码节选:

 DatagramHeaderFrame headerFrame = new DatagramHeaderFrame();
byte[] datagramBytes = new byte[]; byte[] datagramBuffer = (byte[])ar.AsyncState;
byte[] recievedBytes = new byte[numberOfRecievedBytes]; Buffer.BlockCopy(datagramBuffer, , recievedBytes, , numberOfRecievedBytes);
PrasePacking(recievedBytes, numberOfRecievedBytes, ref headerFrame, ref datagramBytes); GetStream().BeginRead(datagramBuffer, , datagramBuffer.Length, HandleDatagramReceived, datagramBuffer);

C++端解析和封装的代码(用Qt写的)

 QByteArray TcpHeaderFrameHelper::bindHeaderAndDatagram(const TcpHeaderFrame &header,const QByteArray &realDataBytes)
{
QByteArray byteArray, temp;
temp.resize(); unsignedToQByteArray((unsigned)header.messageType, temp);
byteArray += temp; unsignedToQByteArray((unsigned)header.featureCode, temp);
byteArray += temp; unsignedToQByteArray((unsigned)header.messageLength, temp);
byteArray += temp; byteArray +=realDataBytes;
return byteArray;
} void TcpHeaderFrameHelper::praseHeaderAndDatagram(const QByteArray &dataBytes,TcpHeaderFrame &headerFrame,QByteArray &realDataBytes)
{
realDataBytes.resize(dataBytes.size() - TcpHeaderFrameHelper::headerSize);
headerFrame.messageType = qByteArrayToInt(dataBytes.left());
headerFrame.featureCode = qByteArrayToInt(dataBytes.mid(,));
headerFrame.messageLength = qByteArrayToInt(dataBytes.mid(,)); realDataBytes = dataBytes.mid(, dataBytes.size());
} unsigned TcpHeaderFrameHelper::qByteArrayToInt(QByteArray bytes)
{
int result = ;
result |= ((bytes[]) & 0x000000ff);
result |= ((bytes[] << ) & 0x0000ff00);
result |= ((bytes[] << ) & 0x00ff0000);
result |= ((bytes[] << ) & 0xff000000); return result;
} void TcpHeaderFrameHelper::unsignedToQByteArray(unsigned num, QByteArray &bytes)
{
bytes.resize();
bytes[] = (char)( 0x000000ff & num);
bytes[] = (char)((0x0000ff00 & (num)) >> );
bytes[] = (char)((0x00ff0000 & (num)) >> );
bytes[] = (char)((0xff000000 & (num)) >> );
}

C#网络编程数据传输中封装数据帧头的方法的更多相关文章

  1. 第84节:Java中的网络编程(中)

    第84节:Java中的网络编程(中) 实现客户端和服务端的通信: 客户端需要的操作,创建socket,明确地址和端口,进行键盘录入,获取需要的数据,然后将录入的数据发送给服务端,为socket输出流, ...

  2. 网络编程-Java中的Internet查询

    前提 在深入理解URL.URI等概念,或者学些Socket相关的知识之,有必要系统理解一下Internet相关的一些基础知识. Internet地址 连接到Internet(因特网)的设备称为节点(n ...

  3. Python网络编程03----Python3.*中socketserver

    socketserver(在Python2.*中的是SocketServer模块)是标准库中一个高级别的模块.用于简化网络客户与服务器的实现(在前面使用socket的过程中,我们先设置了socket的 ...

  4. 三十天学不会TCP,UDP/IP网络编程 -- TCP中的智慧之连续ARQ

    突然发现上一篇文章贴图有问题,关键我怎么调也调不好,为了表达歉意,我再贴一篇gitbook上的吧,虽然违背了我自己的隔一篇在这里发一次的潜规则~其余完整版可以去gitbook(https://www. ...

  5. Java网络编程--Netty中的责任链

    Netty中的责任链 设计模式 - 责任链模式 责任链模式(Chain of Responsibility Pattern)是一种是行为型设计模式,它为请求创建了一个处理对象的链.其链中每一个节点都看 ...

  6. Java网络编程 -- Netty中的ByteBuf

    由于JDK中提供的ByteBuffer无法动态扩容,并且API使用复杂等原因,Netty中提供了ByteBuf.Bytebuf的API操作更加便捷,可以动态扩容,提供了多种ByteBuf的实现,以及高 ...

  7. UNIX网络编程——UDP 中的外出接口的确定

    已连接UDP套接字还可用来确定用于特定目的地的外出接口.这是由connect函数应用到UDP套接字时的一个副作用造成的:内核选择本地IP地址.这个本地IP地址通过为目的IP地址搜索路由表得到外出接口, ...

  8. 《Unix网络编程》中的错误处理函数

    #include "net.h" #include <syslog.h> // syslog() int daemon_proc; static void err_do ...

  9. 网络编程概述和IP地址的获取方法

    java网络通信概述 一.网络通信步骤: 主机1 主机2 QQ-------QQ FEIQ-----FEIQ 1.找到对方IP. 2.找到对方端口号.数据要发送到对方的应用程序上.为了标识这些应用程序 ...

随机推荐

  1. python基础补漏-05-生成器和装饰器

    [1]生成器 很难用简单的语言描述生成器. 生成器:从字面上来理解,就是以某种规则为基础,不断的生成数据的工具 生成器函数: 在函数中如果出现了yield关键字,那么该函数就不再是普通函数,而是生成器 ...

  2. Socket编程实践(2) Socket API 与 简单例程

    在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程.该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端. socket()函 ...

  3. 架构师养成记--11.Executor概述

    常用方法 Executors.newFiexdPool(int nThreads);固定线程数量的线程池: Executors.newSingleThreadExecutor();单个线程的线程池: ...

  4. div+css:div中图片垂直居中

    div中图片垂直居中 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> &l ...

  5. sass接触

    第一句话就是棒棒的,我爱上了. 看了真阿当的文章:<2016年前端技术观察> http://weibo.com/ttarticle/p/show?id=230940405256054051 ...

  6. 【原】小玩node+express爬虫-1

    最近开始重新学习node.js,之前学的都忘了.所以准备重新学一下,那么,先从一个简单的爬虫开始吧. 什么是爬虫 百度百科的解释: 爬虫即网络爬虫,是一种自动获取网页内容的程序.是搜索引擎的重要组成部 ...

  7. 数据结构之平衡查找树(AVL)

    AVL树的旋转操作 图解 最详细 各大教课书上讲的都是左旋与右旋,其实这样很容易理解错误,我们换一种叫法.我们称呼左旋为:逆进针旋转.我们称呼右旋为:顺进针旋转.

  8. 百度地图api调用

    <script type="text/javascript" src="http://api.map.baidu.com/api?key=&v=1.1&am ...

  9. 机器学习——AdaBoost元算法

    当做重要决定时,我们可能会考虑吸取多个专家而不只是一个人的意见.机器学习处理问题也是这样,这就是元算法(meta-algorithm)背后的思路. 元算法是对其他算法进行组合的一种方式,其中最流行的一 ...

  10. eclipse配置tomcat

    1eclipse默认是用workspace的comcat,要把它配置成我们自己的外部tomcat,并且发布路径修改为webapps 2设置tomcat的启动和关闭时间 3如果要发布到tomcat根目录 ...