TCP通信随机延迟排查:ACK包引发的延迟原因咨询
高响应TCP通信延迟问题排查
我正在开发一款要求两台直连以太网PC间实现高响应TCP通信的应用,使用以下代码对TCP Socket响应性进行基准测试:
TCP Server代码
class Program { static void Main(string[] args) { string serverAddress = "192.168.23.88"; // Localhost int serverPort = 6012; TcpListener server = new TcpListener(IPAddress.Parse(serverAddress), serverPort); server.Server.NoDelay = true; server.Start(); Console.WriteLine($"Server started {serverAddress}:{serverPort}..."); while (true) { var client = server.AcceptTcpClient(); client.NoDelay = true; Console.WriteLine("Client connected..."); HandleClientAsync(client); // Fire and forget } } private static void HandleClientAsync(TcpClient client) { using (client) { var buffer = new byte[20]; var stream = client.GetStream(); var t1RP = Encoding.UTF8.GetBytes("<TEST,1>"); var t2RP = Encoding.UTF8.GetBytes("<TEST,2>"); var t3RP = Encoding.UTF8.GetBytes("<TEST,3>"); List<string> consoleList = new List<string>(); while (true) { int bytesRead; try { bytesRead = stream.Read(buffer, 0, buffer.Length); } catch (Exception e) { Console.WriteLine($"Error reading from client: {e.Message}"); foreach (var a in consoleList) Console.WriteLine(a); break; } stream.Write(t1RP, 0, t1RP.Length); stream.Write(t2RP, 0, t2RP.Length); stream.Write(t3RP, 0, t3RP.Length); } } } }
TCP Client代码
static async Task Main(string[] args) { string serverAddress = "192.168.23.88"; // Localhost int serverPort = 6012; using (TcpClient client = new TcpClient()) { try { int cycle = 5000; client.NoDelay = true; Console.WriteLine("Connecting to server..."); await client.ConnectAsync(serverAddress, serverPort); Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} Connected to server {serverAddress}:{serverPort}"); Stopwatch time = new Stopwatch(); Stopwatch elapsedTime = new Stopwatch(); var stream = client.GetStream(); int count = 0; { byte[] buffer = new byte[20]; for (int i = 0; i < cycle; ++i) { long responseTime = 0; long grabTime = 0; long resultTime = 0; byte[] data = Encoding.UTF8.GetBytes("<SOV,1,4,>"); stream.Write(data, 0, data.Length); _ = stream.FlushAsync(); elapsedTime.Restart(); string combined = ""; while (true) { int bytesRead = stream.Read(buffer, 0, buffer.Length); combined += Encoding.UTF8.GetString(buffer, 0, bytesRead); if (combined.Contains("<TEST,1>")) { _ = stream.FlushAsync(); responseTime = elapsedTime.ElapsedMilliseconds; combined = combined.Replace("<TEST,1>", ""); if (responseTime > 3) { Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} SOV Response Time {responseTime}"); responseTime = elapsedTime.ElapsedMilliseconds; count++; } break; } } while (true) { int bytesRead = stream.Read(buffer, 0, buffer.Length); combined += Encoding.UTF8.GetString(buffer, 0, bytesRead); if (combined.Contains("<TEST,2>")) { grabTime = elapsedTime.ElapsedMilliseconds - responseTime; combined = combined.Replace("<TEST,2>", ""); if (grabTime > 3) { _ = stream.FlushAsync(); Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} SOV Grab Time {grabTime}"); grabTime = elapsedTime.ElapsedMilliseconds - responseTime; count++; } break; } } while (true) { int bytesRead = stream.Read(buffer, 0, buffer.Length); combined += Encoding.UTF8.GetString(buffer, 0, bytesRead); if (combined.Contains("<TEST,3>")) { resultTime = elapsedTime.ElapsedMilliseconds - responseTime - grabTime; combined = combined.Replace("<TEST,3>", ""); if (resultTime > 10) { _ = stream.FlushAsync(); Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} SOV Result Time {resultTime}"); resultTime = elapsedTime.ElapsedMilliseconds - responseTime - grabTime; count++; } break; } } time.Restart(); while (time.ElapsedMilliseconds < 50) { } if ((i % 50) == 0) Console.WriteLine($"Complete {i} Cycle"); } } Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} Completed {cycle} Cycle, Exceed Count {count}"); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadLine(); } Console.WriteLine("Connection closed"); }
测试现象
- 5000次循环中随机出现5ms-20ms的响应延迟,WireShark抓包显示Server侧TCP包立即发出,但Client侧存在延迟。
2024年12月10日更新
修改Client代码后,Server发送的3条消息可通过一次Socket.Read()接收,但抓包发现Server侧存在来自Client的[ACK]包引发延迟,该ACK包并非业务消息,咨询此现象原因及TCP配置是否存在遗漏。
问题分析与解决方案
1. Client侧ACK延迟的核心原因
TCP的**延迟ACK(Delayed ACK)**机制是直接诱因。默认情况下,Windows和Linux系统都会启用该机制:接收方收到数据后,不会立即发送ACK,而是等待10-20ms(系统不同略有差异),若这段时间内有要发送给对方的数据,就会把ACK附带在数据里一起发送,以此减少网络包数量。
你的场景中,Client发送业务请求后进入等待状态,这段时间内没有主动向Server发送数据,触发了延迟ACK,导致ACK包延迟发送。Server在未收到ACK时,TCP栈的滑动窗口或拥塞控制逻辑可能间接影响后续数据发送,最终表现为观测到的延迟。
2. 遗漏的TCP配置检查
你已开启NoDelay=true(禁用Nagle算法),但这仅解决了发送方的包合并问题,并未关闭接收方的延迟ACK,需额外配置:
关闭延迟ACK的代码实现
- Windows平台:通过底层Socket的
IOControl设置// 在Client和Server的TcpClient初始化后添加 const int SIO_TCP_SET_ACK_DELAY = -1744830452; byte[] optionIn = BitConverter.GetBytes(0); // 0表示关闭延迟ACK client.Client.IOControl(SIO_TCP_SET_ACK_DELAY, optionIn, null); - Linux平台:使用
TCP_QUICKACK套接字选项const int TCP_QUICKACK = 12; client.Client.SetSocketOption(SocketOptionLevel.Tcp, (SocketOptionName)TCP_QUICKACK, 1);
其他优化点
- 调整接收缓冲区大小:确保缓冲区能容纳Server发送的3条消息,避免因缓冲区不足导致分段等待
client.ReceiveBufferSize = 1024; // 根据实际消息总大小调整 - 替换空循环等待:原代码中
while (time.ElapsedMilliseconds < 50)是空循环,会占用CPU导致线程调度延迟,建议替换为异步等待await Task.Delay(50); - 优化消息读取逻辑:当前字符串匹配方式效率低,建议采用固定格式(比如前缀带消息长度),减少字符串拼接和匹配开销。
3. 验证方法
修改配置后重新抓包,检查Client的ACK包是否在收到Server数据后1ms内发送,同时观测业务延迟是否降至预期范围。
内容的提问来源于stack exchange,提问作者Casey
相关产品推荐
相关产品推荐

