最近的项目在做socket通信报文解析的时候,用到了结构体与字节数组的转换;由于客户端采用Python开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性,这一点非常重要。
首先是结构体定义
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct DataPack
{
public int weldNum;
public float x;
public float y;
public float z;
//字符串,SizeConst为字符串的最大长度
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string timeStamp;
//结构体里的构造函数必须是带参数的
public DataPack(int n, float x, float y, float z, string t)
{
weldNum = n;
this.x = x;
this.y = y;
this.z = z;
timeStamp = t;
}
}
需要说明的是:
1.[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
公共语言运行库利用StructLayoutAttribute控制类或结构的数据字段在托管内存中的物理布局,即类或结构需要按某种方式排列。如果要将类传递给需要指定布局的非托管代码,则显式控制类布局是重要的。
1)LayoutKind.Sequential:CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同,即按照结构中占用空间最大的成员进行对齐(Align);
2)LayoutKind.Explicit:CLR不对结构体进行任何内存对齐(Align);
3)LayoutKind.Auto:CLR会对结构体中的字段顺序进行调整,使实例占有尽可能少的内存,并进行4byte的内存对齐(Align)
2.[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
结构体中每个成员的长度都是需要明确的,因为内存需要根据这个分配空间,这里我们需要在成员声明时进行定义;
string类型实际为Char[20]长度的数组,实际使用中只能有效的使用前19个字符,因为char[20]最后一位默认\0;
其次,
结构体与字节数组的互转
<summary>
/// 结构体转byte数组
/// </summary>
/// <param name="structObj">要转换的结构体</param>
/// <returns>转换后的byte数组</returns>
public static byte[] StructToBytes(object structObj)
{
//得到结构体的大小
int size = Marshal.SizeOf(structObj);
//创建byte数组
byte[] bytes = new byte[size];
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将结构体拷到分配好的内存空间
Marshal.StructureToPtr(structObj, structPtr, false);
//从内存空间拷到byte数组
Marshal.Copy(structPtr, bytes, 0, size);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回byte数组
return bytes;
}
/// <summary>
/// byte数组转结构体
/// </summary>
/// <param name="bytes">byte数组</param>
/// <param name="type">结构体类型</param>
/// <returns>转换后的结构体</returns>
public static object BytesToStuct(byte[] bytes, Type type)
{
//得到结构体的大小
int size = Marshal.SizeOf(type);
//byte数组长度小于结构体的大小
if (size > bytes.Length)
{
//返回空
return null;
}
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将byte数组拷到分配好的内存空间
Marshal.Copy(bytes, 0, structPtr, size);
//将内存空间转换为目标结构体
object obj = Marshal.PtrToStructure(structPtr, type);
//释放内存空间
Marshal.FreeHGlobal(structPtr);
//返回结构体
return obj;
}
最后,
Socket服务器与客户端,使用的是异步非阻塞的方式
服务器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//额外导入的两个命名空间
using System.Net.Sockets;
using System.Net;
namespace SocketServer
{
class Program
{
/// <summary>
/// Socket Server 演示
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
//创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//将该socket绑定到主机上面的某个端口
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx
socket.Bind(new IPEndPoint(IPAddress.Any, 4530));
//启动监听,并且设置一个最大的队列长度
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx
socket.Listen(4);
//开始接受客户端连接请求
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx
socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);
Console.WriteLine("Server is ready!");
Console.Read();
}
public static void ClientAccepted(IAsyncResult ar)
{
var socket = ar.AsyncState as Socket;
//这就是客户端的Socket实例,我们后续可以将其保存起来
var client = socket.EndAccept(ar);
//给客户端发送一个欢迎消息
client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at " + DateTime.Now.ToString()));
//实现每隔两秒钟给服务器发一个消息
//这里我们使用了一个定时器
var timer = new System.Timers.Timer();
timer.Interval = 2000D;
timer.Enabled = true;
timer.Elapsed += (o, a) =>
{
//检测客户端Socket的状态
if(client.Connected)
{
try
{
client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString()));
}
catch(SocketException ex)
{
Console.WriteLine(ex.Message);
}
}
else
{
timer.Stop();
timer.Enabled = false;
Console.WriteLine("Client is disconnected, the timer is stop.");
}
};
timer.Start();
//接收客户端的消息(这个和在客户端实现的方式是一样的)
client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), client);
//准备接受下一个客户端请求
socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);
}
static byte[] buffer = new byte[1024];
public static void ReceiveMessage(IAsyncResult ar)
{
try
{
var socket = ar.AsyncState as Socket;
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
var length = socket.EndReceive(ar);
//读取出来消息内容
var message = Encoding.Unicode.GetString(buffer, 0, length);
//显示消息
Console.WriteLine(message);
//接收下一个消息,递归的调用
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
}
catch(Exception ex){
Console.WriteLine(ex.Message);
}
}
}
}
客户端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//导入的命名空间
using System.Net.Sockets;
namespace SocketClient
{
class Program
{
/// <summary>
/// Socket Client 演示
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
//创建一个Socket
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//连接到指定服务器的指定端口
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.connect.aspx
socket.Connect("localhost", 4530);
Console.WriteLine("connect to the server");
//实现接受消息的方法
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginreceive.aspx
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
//接受用户输入,将消息发送给服务器端
while(socket.Connected)
{
var message = "Message from client : " + DateTime.Now.ToString();//Console.ReadLine();
var outputBuffer = Encoding.Unicode.GetBytes(message);
socket.BeginSend(outputBuffer, 0, outputBuffer.Length, SocketFlags.None, null, null);
}
}
static byte[] buffer = new byte[1024];
public static void ReceiveMessage(IAsyncResult ar)
{
try
{
var socket = ar.AsyncState as Socket;
//方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx
var length = socket.EndReceive(ar);
//读取出来消息内容
var message = Encoding.Unicode.GetString(buffer, 0, length);
//显示消息
Console.WriteLine(message);
//接收下一个消息,递归的调用
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
参考:
https://www.cnblogs.com/dafanjoy/p/7818126.html
http://www.cnblogs.com/chenxizhang/archive/2011/09/10/2172994.html