C#套接字接收不完整数据

问题描述 投票:0回答:1

我正在开发一对服务器/客户端应用程序,它们作为远程文件浏览器一起工作,例如 Filezilla。
TCP套接字用于通信,这是它们用来处理通信的中心类:

public class SocketHelper
{
    public static async Task SendAsync(Socket socket, object message)
    {
        // Size
        var sendBuffer = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
        await socket.SendAsync(BitConverter.GetBytes(sendBuffer.Length));

        // Payload
        var sentBytes = 0;

        while (sentBytes < sendBuffer.Length)
        {
            sentBytes += await socket.SendAsync(sendBuffer);
        }
    }

    public static async Task<string> ReceiveAsync(Socket socket)
    {
        // Size
        var preflightBuffer = new byte[32];
        await socket.ReceiveAsync(preflightBuffer);

        // Payload
        var receiveBuffer = new byte[BitConverter.ToInt32(preflightBuffer)];
        var receivedBytes = 0;

        while (receivedBytes < receiveBuffer.Length)
        {
            receivedBytes += await socket.ReceiveAsync(receiveBuffer);
        }

        return Encoding.UTF8.GetString(receiveBuffer);
    }
}

如您所见,我首先发送大小,然后计算每次发送和接收的字节数,以协调和限制我发送和接收的数量。
这对于小型“消息”非常有效,例如用于目录列表的简单序列化请求/响应对象,如下所示:

public class FolderListingRequest
{
    public string Path { get; set; }
}

public class FolderListingResponse : Response
{
    public IList<FileData> Files { get; set; } = [];
}

但是,对于大量数据(文件下载),它会崩溃,因为在传输过程中的某个地方,消息要么不完整,要么最后有额外的数据,导致反序列化失败。
此外,它在失败时完全不一致。有时一次发送 5 个文件效果很好,但有时发送一个文件就会中断。

这是请求和处理文件下载的服务器代码:

public async Task DownloadFiles(Socket socket, DownloadFilesRequest request)
{
    var payload = new Payload
    {
        Type = PayloadTypeEnum.DownloadFiles,
        Data = request,
    };
    var fileCountToReceive = request.Paths.Count;
    var fileCountReceived = 0;
    await SocketHelper.SendAsync(socket, payload);

    while (fileCountReceived < fileCountToReceive)
    {
        Console.WriteLine("Receiving file");
        var receivedData = await SocketHelper.ReceiveAsync(socket);
        var fileData = JsonConvert.DeserializeObject<FileData>(receivedData);

        if (fileData == null)
        {
            Console.WriteLine("Could not deserialize downloaded file info");
        }

        localFileService.StoreFile(fileData);
        fileCountReceived++;
    }
}

这是客户端发送文件的部分:

public async Task SendFiles(Socket socket, DownloadFilesRequest request)
{
    foreach (var path in request.Paths)
    {
        var response = new FileData()
        {
            Path = path,
            Contents = localFileService.GetFileContents(path),
        };

        await SocketHelper.SendAsync(socket, response);
    }
}

这是否可能是某种同步问题,即客户端发送的数据多于服务器设计接收的数据?计算字节数难道不足以缓解这个问题吗?我应该使用标记来代替消息的开头和结尾吗? 或者我试图一次发送太多数据?我应该把它分块吗?

有什么想法吗?

c# sockets
1个回答
0
投票

你这里有几个错误

  • preflightBuffer
    接收中的大小错误,应该是 4 个字节。
  • 读写循环需要传递偏移量。

您可以使用

TcpClient
通过 TCP 套接字获取
NetworkStream
,而不是搞乱原始套接字和读取循环,然后就可以使用
ReadExactlyAsync

public static async Task SendAsync(Stream stream, object message)
{
    // Size
    var sendBuffer = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
    await stream.SendAsync(BitConverter.GetBytes(sendBuffer.Length));

    // Payload
    await stream.WriteAsync(sendBuffer);
}

public static async Task<string> ReceiveAsync(Stream stream)
{
    // Size
    var preflightBuffer = new byte[4];
    await socket.ReadExactlyAsync(preflightBuffer);

    // Payload
    var receiveBuffer = new byte[BitConverter.ToInt32(preflightBuffer)];
    await socket.ReadExactlyAsync(receiveBuffer);
    return Encoding.UTF8.GetString(receiveBuffer);
}

您可以通过使用

ArrayPool
并切换到 System.Text.Json 来提高效率,但说实话,您可能最好使用适当的协议,例如 gRPC 或 SignalR。

© www.soinside.com 2019 - 2024. All rights reserved.