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

C# Socket分块上传文件断开重连后服务端接收数据报错如何解决

问题根因

  • TCP原生是有状态连接,当前实现未开启TCP保活检测,断网时两端Socket实例无法感知连接失效,15秒后操作系统回收失效连接后,仍用旧Socket实例收发数据就会触发该报错。
  • ReadDataAsync 实现逻辑存在缺陷:用 socket.Available 判断数据是否接收完成不可靠,网络延迟场景下会提前终止读取导致包体不完整,也没有处理 ReceiveAsync 返回0的连接断开标识。
  • 服务端仅调用一次 Accept() 处理单连接,客户端断连后重新发起的连接不会被服务端接收,服务端仍在监听已失效的旧Socket,自然无法收到数据。
  • 上传任务与Socket连接强绑定,连接断开后没有状态留存和续传机制,恢复连接后无法继续之前的上传流程。

修复方案

1. 连接层修复:开启TCP保活,检测失效连接

客户端和服务端创建Socket实例后,统一添加保活配置,及时感知死连接:

// 开启Socket保活
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
// 配置保活规则:3秒无活动就开始发送探测包,每隔1秒发1次,连续5次无响应判定连接失效
socket.IOControl(IOControlCode.KeepAliveValues, new byte[] { 1, 3000, 1000 }, null);

2. 修正读写逻辑,解决读包不全、连接未感知问题

首先自定义传输包结构:包头4字节存储整个包的总长度,后续跟着包体内容,不要用Available判断接收结束:

修正后的SendDataAsync

public async Task SendDataAsync(Socket socket, IEnumerable<byte> data)
{
    if (data == null || !data.Any()) return;
    var dataArray = data.ToArray();
    // 先发包长度(4字节包头)
    var header = BitConverter.GetBytes(dataArray.Length);
    await SendAllBytesAsync(socket, header);
    // 再发包体
    await SendAllBytesAsync(socket, dataArray);
}

// 新增辅助方法:保证所有字节全部发送完成
private async Task SendAllBytesAsync(Socket socket, byte[] data)
{
    int totalSent = 0;
    while (totalSent < data.Length)
    {
        var sent = await socket.SendAsync(new ArraySegment<byte>(data, totalSent, data.Length - totalSent), SocketFlags.None);
        if (sent == 0) throw new IOException("连接已断开");
        totalSent += sent;
    }
}

修正后的ReadDataAsync

public async Task<T> ReadDataAsync<T>(Socket socket, Func<IEnumerable<byte>, T> parsePacket)
{
    // 先读4字节包头,获取包总长度
    var headerBuffer = new byte[4];
    await ReceiveAllBytesAsync(socket, headerBuffer);
    var totalLength = BitConverter.ToInt32(headerBuffer, 0);
    // 再读完整包体
    var bodyBuffer = new byte[totalLength];
    await ReceiveAllBytesAsync(socket, bodyBuffer);
    return parsePacket(bodyBuffer);
}

// 新增辅助方法:保证读取到指定长度的所有字节
private async Task ReceiveAllBytesAsync(Socket socket, byte[] buffer)
{
    int totalReceived = 0;
    while (totalReceived < buffer.Length)
    {
        var received = await socket.ReceiveAsync(new ArraySegment<byte>(buffer, totalReceived, buffer.Length - totalReceived), SocketFlags.None);
        if (received == 0) throw new IOException("连接已断开");
        totalReceived += received;
    }
}

3. 服务端支持多连接与续传

  • 调整服务端连接处理逻辑,每次Accept到新连接后放到独立异步任务处理,继续监听新的连接请求:
listenSocket.Bind(ipPoint);
listenSocket.Listen(20);
Console.WriteLine("Waiting for connections...");
while (!StopServer())
{
    var socket = await listenSocket.AcceptAsync();
    // 新连接丢到独立任务处理,不阻塞Accept
    _ = Task.Run(async () =>
    {
        try
        {
            await _serverProcess.ProcessAsync(socket, StopServer);
        }
        catch (Exception ex)
        {
            // 处理连接异常,释放资源
            socket.Close();
            socket.Dispose();
        }
    });
}
  • 上传任务状态与客户端ID、文件哈希绑定,不要与Socket绑定,收到客户端续传请求时返回当前已上传的偏移量,让客户端从对应位置继续发送分块。

4. 客户端增加重连续传逻辑

客户端收发数据时捕获SocketException、IOException,一旦触发连接异常,销毁旧Socket实例,重新建立新连接,携带文件哈希、已上传偏移量向服务端发起续传请求,收到服务端确认后从对应偏移量继续发送剩余分块。

内容的提问来源于stack exchange,提问作者Lev Kostychenko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 09:36:00