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

TCP Socket重启接收端后结束标记无法可靠送达问题排查

问题分析与解决方案:TCP半关闭连接导致的接收端异常

问题根源

核心问题是发送端被强制终止(kill命令)时,未完成TCP连接的优雅关闭流程,结合原发送逻辑的两个隐患共同导致:

  1. 忽略Socket.Send()的返回值:该方法返回实际写入发送缓冲区的字节数,原代码直接丢弃返回值,可能存在数据未完全写入就终止进程的情况,包括作为结束标记的5字节。
  2. 优雅关闭逻辑未执行:如果进程被kill,socket.Shutdown(SocketShutdown.Send)根本无法运行,发送端不会向接收端发送FIN包,导致接收端处于半关闭连接状态——持续调用BeginReceive时收到0字节(表示连接已关闭),但因为没收到约定的结束标记,陷入无限循环,CPU占用飙升。

你怀疑的Windows套接字复用(TIME_WAIT状态)并非直接原因,但残留的半关闭连接会影响新连接的正常交互,重启机器或换端口能解决是因为彻底清除了残留的连接状态。

除更换端口外获取“干净”Socket的方法

  • 配置LingerOption:强制Socket关闭时等待剩余数据发送完成并确认,避免半关闭连接残留
  • 启用SO_REUSEADDR选项:允许绑定处于TIME_WAIT状态的端口,解决端口复用问题,避免因旧连接状态导致的端口占用
  • 确保发送端优雅退出:禁用kill命令终止发送端,改用信号量、退出指令等方式,让发送端完成数据发送和Socket清理流程

代码优化方案

发送端优化(解决根源问题)

修复Send逻辑的完整性,确保数据全部发送,并强化优雅关闭机制:

using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
    socket.SendBufferSize = 0x10000;
    // 配置 linger 选项:关闭时等待2秒发送剩余数据,避免数据丢失
    socket.LingerState = new LingerOption(true, 2);
    // 允许复用处于TIME_WAIT状态的端口
    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

    socket.Connect(this.Host, this.Port);

    bool oneOrMoreSent = false;

    while (this.queue.TryDequeue(out IMessage? msg))
    {
        if (msg == null) continue;
        byte[] packet = (msg as Message).Packet ??= CreatePacket(msg);
        int bytesSent = 0;
        // 循环发送直到所有字节写入缓冲区
        while (bytesSent < packet.Length)
        {
            bytesSent += socket.Send(packet, bytesSent, packet.Length - bytesSent, SocketFlags.None);
        }
        oneOrMoreSent = true;
    }

    if (oneOrMoreSent)
    {
        int endBytesSent = 0;
        // 确保结束标记完全发送
        while (endBytesSent < End.Length)
        {
            endBytesSent += socket.Send(End, endBytesSent, End.Length - endBytesSent, SocketFlags.None);
        }
    }

    // 强制发送FIN包,通知接收端发送完成
    socket.Shutdown(SocketShutdown.Send);
}

接收端优化(最终落地方案)

保留三次短等待的兜底逻辑,同时结合TCP连接关闭信号(收到0字节),避免无限循环:

// 示例接收逻辑伪代码
int emptyReceiveCount = 0;
while (true)
{
    var result = socket.EndReceive(ar);
    if (result == 0)
    {
        emptyReceiveCount++;
        // 连续3次收到0字节,判定连接已关闭
        if (emptyReceiveCount >= 3)
        {
            break;
        }
        // 短等待后重试,避免误判
        Thread.Sleep(1);
        socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, socket);
        continue;
    }
    emptyReceiveCount = 0;
    // 处理收到的数据
    ProcessReceivedData(buffer, result);
    // 检查是否收到结束标记
    if (CheckForEndMarker(buffer))
    {
        break;
    }
    // 继续接收下一批数据
    socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, socket);
}
// 优雅关闭接收端Socket
socket.Shutdown(SocketShutdown.Both);
socket.Dispose();

关键注意事项

  • TCP是流协议,不能仅依赖自定义结束标记判断传输完成,必须同时处理连接关闭的信号(收到0字节)
  • 禁止用kill命令终止发送端,改用优雅退出机制,确保Socket的Shutdown和Dispose逻辑执行完成
  • 所有Send操作必须确认全部字节写入缓冲区,避免因缓冲区未满导致的数据丢失

内容的提问来源于stack exchange,提问作者Martin Maat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 10:10:31