C# TCP/IP服务开发:多结构体发送与接收实现方案求助
C# 多连接TCP服务:结构体收发与序列化实现指导
核心问题修正点
1. 结构体可空类型序列化问题
原ResponseMessage中的可空值类型(byte?、short?等)无法被Marshal正确序列化,因为可空类型本质是嵌套结构体,内存布局和定义的Size=12不匹配。需改为非可空类型,用约定默认值表示空状态(例如用0表示未设置)。
2. 服务器多连接并发处理
原服务器采用同步循环处理连接,同一时间只能处理一个客户端。需改为异步并行处理每个连接,避免阻塞后续请求。
3. TCP粘包/拆包处理
TCP是流式协议,连续发送的结构体可能被合并或拆分。需按结构体固定长度读取数据,确保每次反序列化一个完整的结构体。
4. 序列化/反序列化逻辑优化
确保序列化时仅处理结构体对应长度的字节,反序列化时只使用读取到的有效数据,避免内存越界或数据错乱。
修正后的客户端代码
using System; using System.Collections.Generic; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading.Tasks; namespace TcpStructClient { class Program { static async Task Main(string[] args) { List<ResponseMessage> messages = new List<ResponseMessage>(); messages.Add( new ResponseMessage() { cmd = 2, cmd_num = 4, client_id = 5, local_part_num = 1, end_buffer_address = 100500, group_id = 1 } ); messages.Add( new ResponseMessage() { cmd = 2, cmd_num = 24, client_id = 11, local_part_num = 25, end_buffer_address = 300500, group_id = 33 } ); using TcpClient tcpClient = new TcpClient(); try { await tcpClient.ConnectAsync("127.0.0.1", 8888); var stream = tcpClient.GetStream(); foreach (var msg in messages) { var byteArray = StructureToByteArray(msg); await stream.WriteAsync(byteArray, 0, byteArray.Length); // 可选:添加小延迟避免粘包(生产环境建议用长度前缀) await Task.Delay(10); } Console.WriteLine("所有消息发送完成"); } catch (Exception ex) { Console.WriteLine($"发送失败:{ex.Message}"); } } /// <summary> /// 将结构体转换为字节数组 /// </summary> static byte[] StructureToByteArray(object obj) { int size = Marshal.SizeOf(obj); byte[] arr = new byte[size]; IntPtr ptr = Marshal.AllocHGlobal(size); try { Marshal.StructureToPtr(obj, ptr, false); Marshal.Copy(ptr, arr, 0, size); } finally { Marshal.FreeHGlobal(ptr); } return arr; } [StructLayout(LayoutKind.Sequential, Size = 12)] struct RequestMessage { public sbyte cmd; public byte cmd_num; public short rsvd; public int request_address; public int request_size; public RequestMessage() { cmd = 1; cmd_num = 200; rsvd = 0; request_address = 192133333; request_size = 350350333; } } [StructLayout(LayoutKind.Sequential, Size = 12, Pack = 1)] struct ResponseMessage { public sbyte cmd; public byte cmd_num; public short local_part_num; public short group_id; public short client_id; public int end_buffer_address; public ResponseMessage() { cmd = 2; cmd_num = 200; local_part_num = 1; group_id = 1; client_id = 4; end_buffer_address = 123213123; } } } }
修正后的服务器代码
using System; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace TcpStructServer { class Program { static async Task Main(string[] args) { var tcpListener = new TcpListener(IPAddress.Any, 8888); try { tcpListener.Start(); Console.WriteLine("服务器已启动,等待客户端连接..."); int clientCounter = 0; while (true) { var tcpClient = await tcpListener.AcceptTcpClientAsync(); clientCounter++; // 异步处理客户端连接,不阻塞后续连接 _ = HandleClientAsync(tcpClient, clientCounter); } } catch (Exception ex) { Console.WriteLine($"服务器异常:{ex.Message}"); tcpListener.Stop(); } } static async Task HandleClientAsync(TcpClient tcpClient, int clientId) { Console.WriteLine($"客户端 {clientId} 已连接"); var stream = tcpClient.GetStream(); int structSize = Marshal.SizeOf(typeof(ResponseMessage)); byte[] buffer = new byte[structSize]; try { while (true) { // 按结构体固定长度读取数据 int bytesRead = await stream.ReadAsync(buffer, 0, structSize); if (bytesRead == 0) { Console.WriteLine($"客户端 {clientId} 断开连接"); break; } // 确保读取到完整的结构体 if (bytesRead == structSize) { ResponseMessage msg = FromBytes(buffer); StringBuilder sb = new StringBuilder(); sb.Append($"客户端 {clientId} 收到消息:"); sb.Append($"cmd={msg.cmd}, "); sb.Append($"cmd_num={msg.cmd_num}, "); sb.Append($"local_part_num={msg.local_part_num}, "); sb.Append($"group_id={msg.group_id}, "); sb.Append($"client_id={msg.client_id}, "); sb.Append($"end_buffer_address={msg.end_buffer_address}"); Console.WriteLine(sb.ToString()); } else { Console.WriteLine($"客户端 {clientId} 数据不完整,已丢弃"); } } } catch (Exception ex) { Console.WriteLine($"客户端 {clientId} 异常:{ex.Message}"); } finally { tcpClient.Close(); } } static ResponseMessage FromBytes(byte[] arr) { ResponseMessage msg = new ResponseMessage(); int size = Marshal.SizeOf(msg); IntPtr ptr = IntPtr.Zero; try { ptr = Marshal.AllocHGlobal(size); Marshal.Copy(arr, 0, ptr, size); msg = (ResponseMessage)Marshal.PtrToStructure(ptr, typeof(ResponseMessage)); } finally { Marshal.FreeHGlobal(ptr); } return msg; } [StructLayout(LayoutKind.Sequential, Size = 12)] struct RequestMessage { public sbyte cmd; public byte cmd_num; public short rsvd; public int request_address; public int request_size; public RequestMessage() { cmd = 1; cmd_num = 200; rsvd = 0; request_address = 192133333; request_size = 350350333; } } [StructLayout(LayoutKind.Sequential, Size = 12, Pack = 1)] struct ResponseMessage { public sbyte cmd; public byte cmd_num; public short local_part_num; public short group_id; public short client_id; public int end_buffer_address; public ResponseMessage() { cmd = 2; cmd_num = 200; local_part_num = 1; group_id = 1; client_id = 4; end_buffer_address = 123213123; } } } }
关键说明
- 结构体内存布局:添加
Pack=1确保结构体按1字节对齐,避免不同平台下内存布局差异。 - 多连接处理:服务器用
_ = HandleClientAsync(...)异步处理每个客户端,实现并发连接支持。 - 粘包处理:按结构体固定长度读取,确保每次获取完整的结构体数据。如果结构体长度不固定,可先发送4字节的长度前缀,再发送结构体数据。
- 序列化可靠性:移除可空类型,改用非可空类型+默认值的方式,确保
Marshal能正确序列化/反序列化。
内容的提问来源于stack exchange,提问作者RxNsx
相关产品推荐
相关产品推荐

