我正在开发一对服务器/客户端应用程序,它们作为远程文件浏览器一起工作,例如 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);
}
}
这是否可能是某种同步问题,即客户端发送的数据多于服务器设计接收的数据?计算字节数难道不足以缓解这个问题吗?我应该使用标记来代替消息的开头和结尾吗? 或者我试图一次发送太多数据?我应该把它分块吗?
有什么想法吗?
你这里有几个错误
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。