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
相关产品推荐
相关产品推荐

