如何提升同一主机上C#与SoftPLC的TCP Socket通信速率
问题分析与解决方案
核心问题
NoDelay设置对象错误:你给监听套接字listener设置了NoDelay,但这个属性需要作用在**连接后的通信套接字handler**上——监听套接字仅负责接收连接请求,不处理数据传输,所以之前的设置完全无效。- TCP流粘包特性:TCP是面向流的协议,操作系统默认会合并小数据包发送(Nagle算法),或在接收端将多个到达的数据包合并到缓冲区,导致你一次读取到多个100ms间隔的数据包,表现为700ms才收到一次。
- 接收逻辑依赖无效标记:你的代码仅在收到
<|EOM|>时才输出内容,但如果SoftPLC未发送该标记,或标记未与每个数据包绑定,就会导致数据堆积,直到缓冲区积累到一定量才被读取。
具体修复步骤
1. 正确禁用Nagle算法
将NoDelay设置到通信套接字handler上,避免小数据包合并:
var handler = await listener.AcceptAsync(); handler.NoDelay = true; // 关键:作用在实际传输数据的套接字上
2. 实现固定长度拆包逻辑
由于你的每个数据包是固定250字节,直接按固定长度拆分数据即可,无需依赖EOM标记:
using System.Net; using System.Net.Sockets; IPEndPoint ipEndPoint = new(IPAddress.Parse("127.0.0.1"), 13001); using Socket listener = new( ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listener.Bind(ipEndPoint); listener.Listen(100); var handler = await listener.AcceptAsync(); handler.NoDelay = true; // 禁用Nagle算法,避免小包合并 const int PacketSize = 250; // 固定数据包长度 byte[] buffer = new byte[1024]; // 复用缓冲区,减少内存分配 int remainingBytes = 0; // 记录缓冲区中未处理的剩余字节数 while (true) { // 接收数据到缓冲区的剩余位置 int received = await handler.ReceiveAsync(buffer.AsMemory(remainingBytes), SocketFlags.None); if (received == 0) break; // 客户端断开连接 remainingBytes += received; // 循环处理所有完整的数据包 while (remainingBytes >= PacketSize) { // 提取一个完整的250字节数据包 byte[] packet = new byte[PacketSize]; Array.Copy(buffer, 0, packet, 0, PacketSize); // 这里替换成你的业务处理逻辑,比如解析PLC数据 string content = System.Text.Encoding.UTF8.GetString(packet); Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] 收到数据包:{content}"); // 将剩余未处理的数据移到缓冲区开头 remainingBytes -= PacketSize; Array.Copy(buffer, PacketSize, buffer, 0, remainingBytes); } }
3. 额外优化建议
- 复用缓冲区:避免在循环内重复创建缓冲区对象,减少GC开销。
- 添加异常处理:生产环境中需捕获
SocketException等异常,避免程序意外崩溃。 - 确认PLC发送逻辑:检查SoftPLC是否确实每100ms发送一个完整的250字节数据包,无内部合并发送的设置。
内容的提问来源于stack exchange,提问作者Leandro Teixeira
相关产品推荐
相关产品推荐

