You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中通过UDP发送包含内部结构体、数组和枚举的结构体

C#自定义结构体UDP序列化与反序列化(无程序集依赖方案)

问题背景

使用Visual Studio 2019开发C# UI程序,需要通过UDP发送包含嵌套结构体、固定长度数组和枚举的自定义结构体。尝试过BinaryFormatter但存在程序集依赖问题——接收端必须引用发送端程序集才能反序列化,需要一种无需依赖的字节数组序列化/反序列化方案。

解决方案:使用Marshal手动序列化/反序列化

利用System.Runtime.InteropServices.Marshal类结合已有的[MarshalAs]特性,可以直接将结构体转换为字节数组。接收端只需定义完全一致的结构体(字段顺序、类型、MarshalAs属性必须匹配)即可完成反序列化,无需引用发送端程序集。

1. 通用序列化/反序列化工具方法

using System.Runtime.InteropServices;

public static class StructConverter
{
    // 将结构体转为字节数组
    public static byte[] StructToBytes<T>(T structure) where T : struct
    {
        int size = Marshal.SizeOf(structure);
        byte[] buffer = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);
        try
        {
            Marshal.StructureToPtr(structure, ptr, false);
            Marshal.Copy(ptr, buffer, 0, size);
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
        return buffer;
    }

    // 将字节数组转为结构体
    public static T BytesToStruct<T>(byte[] bytes) where T : struct
    {
        int size = Marshal.SizeOf<T>();
        if (bytes.Length != size)
            throw new ArgumentException("字节数组长度与结构体大小不匹配");
        
        IntPtr ptr = Marshal.AllocHGlobal(size);
        try
        {
            Marshal.Copy(bytes, 0, ptr, size);
            return Marshal.PtrToStructure<T>(ptr);
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

2. 修正后的发送端代码

原代码存在两处错误需修正:

  • loc2的初始化错误(误给loc1赋值)
  • arc1.length和arc2.length的元素数量与SizeConst=5不匹配,需补全5个元素
namespace Sender
{
    public struct FMS
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public arc[] arcs;
        public UInt32 type;
    }
    public struct arc
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public location[] locs;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
        public sbyte[] length;
        public UInt32 fuel;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
        public char[] ids;
        public distance dist;
    }
    public struct location
    {
        public float x_axis;
        public float y_axis;
    }
    public enum distance
    {
        PEAK = 0,
        STEP
    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Send_Button_Click(object sender, EventArgs e)
        {
            Socket clientSocket = new Socket(SocketType.Dgram, ProtocolType.Udp);
            IPAddress clientIpAddress = IPAddress.Parse("127.0.0.1");
            int clientPortNum = 60000;
            IPEndPoint clientEndPoint = new IPEndPoint(clientIpAddress, clientPortNum);
            clientSocket.Bind(clientEndPoint);

            IPAddress serverIpAddress = IPAddress.Parse("127.0.0.1");
            int serverPortNum = 50000;
            IPEndPoint serverEndPoint = new IPEndPoint(serverIpAddress, serverPortNum);

            // 初始化结构体(修正原代码错误)
            location loc1 = new location();
            loc1.x_axis = 1.0F;
            loc1.y_axis = 2.0F;

            location loc2 = new location();
            loc2.x_axis = 3.0F; // 修正:原代码误赋值给loc1
            loc2.y_axis = 4.0F;

            arc arc1 = new arc();
            arc1.locs = new location[] { loc1, loc2 };
            arc1.length = new sbyte[] { 1, 2, 3, 4, 5 }; // 修正:补全5个元素
            arc1.fuel = 3500;
            arc1.ids = new char[] { 'a', 'b', 'c', 'd', 'e' };
            arc1.dist = distance.PEAK;

            location loc3 = new location();
            loc3.x_axis = 5.0F;
            loc3.y_axis = 6.0F;

            location loc4 = new location();
            loc4.x_axis = 7.0F;
            loc4.y_axis = 8.0F;

            arc arc2 = new arc();
            arc2.locs = new location[] { loc3, loc4 };
            arc2.length = new sbyte[] { 5, 6, 7, 8, 9 }; // 修正:补全5个元素
            arc2.fuel = 7000;
            arc2.ids = new char[] { 'x', 'c', 'g', 'f', 'a' };
            arc2.dist = distance.STEP;

            FMS fmsObj = new FMS();
            fmsObj.arcs = new arc[] { arc1, arc2 };
            fmsObj.type = 1;

            // 序列化结构体为字节数组
            byte[] bytesToSend = StructConverter.StructToBytes(fmsObj);
            
            clientSocket.SendTo(bytesToSend, serverEndPoint);
            clientSocket.Close();
        }
    }
}

3. 接收端代码示例

接收端只需定义完全一致的结构体(字段顺序、类型、MarshalAs属性必须和发送端完全相同),然后调用反序列化方法:

namespace Receiver
{
    // 与发送端完全一致的结构体定义
    public struct FMS
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public arc[] arcs;
        public UInt32 type;
    }
    public struct arc
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public location[] locs;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
        public sbyte[] length;
        public UInt32 fuel;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
        public char[] ids;
        public distance dist;
    }
    public struct location
    {
        public float x_axis;
        public float y_axis;
    }
    public enum distance
    {
        PEAK = 0,
        STEP
    }

    public partial class ReceiverForm : Form
    {
        private Socket serverSocket;

        public ReceiverForm()
        {
            InitializeComponent();
            StartListening();
        }

        private void StartListening()
        {
            serverSocket = new Socket(SocketType.Dgram, ProtocolType.Udp);
            IPAddress serverIp = IPAddress.Parse("127.0.0.1");
            int serverPort = 50000;
            IPEndPoint serverEndPoint = new IPEndPoint(serverIp, serverPort);
            serverSocket.Bind(serverEndPoint);
            
            // 异步接收数据
            byte[] buffer = new byte[Marshal.SizeOf<FMS>()];
            EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
            serverSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref remoteEndPoint, ReceiveCallback, buffer);
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                byte[] buffer = (byte[])ar.AsyncState;
                EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                int bytesRead = serverSocket.EndReceiveFrom(ar, ref remoteEndPoint);
                
                // 反序列化字节数组为结构体
                FMS receivedFms = StructConverter.BytesToStruct<FMS>(buffer);
                
                // 在UI线程更新显示(如果需要)
                this.Invoke(new Action(() =>
                {
                    // 处理接收到的结构体数据
                    MessageBox.Show($"接收到Type: {receivedFms.type}");
                }));

                // 继续监听下一个数据包
                serverSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref remoteEndPoint, ReceiveCallback, buffer);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"接收错误: {ex.Message}");
            }
        }
    }
}

注意事项

  • 结构体的字段顺序必须严格一致,Marshal序列化依赖字段在内存中的布局顺序
  • 所有[MarshalAs]属性的参数(如SizeConst)必须和发送端完全匹配
  • 枚举类型的底层类型默认是int,保持和发送端定义一致即可,Marshal会自动处理其数值转换
  • 可通过[StructLayout(LayoutKind.Sequential, Pack = 1)]强制指定内存布局和对齐方式,减少跨平台差异

内容的提问来源于stack exchange,提问作者Erencan Tekin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 20:17:10