C# SuperSimpleTCP:接收到的文件部分损坏

C# SuperSimpleTCP: Received file is partially corrupted

我正在研究基于 TCP (SuperSimpleTCP) 的文件传输应用程序。该应用程序应允许 LAN 内计算机之间的文件传输。它部分这样做 - 问题是,接收到的文件部分损坏(参见屏幕截图:https://imgur.com/a/OnIovIr)。

图像的顶部始终保存正确,但是图像的其余部分在随机点处损坏 - 可能在下方一半处或仅低于顶部几个像素。我不太明白为什么会这样。

我认为问题可能在于接收包的组装方式或数据包根本没有到达服务器(尽管有 TCP)。

所以我尝试了在服务器站点组装它们的不同方法,比如将它们收集到一个列表中,然后重写为一个字节[],最后保存。我还让客户端在发送最后一个文件时发送“ack”消息,允许服务器保存文件。

但是,上述 none 改变了文件的行为方式。

这是客户端class中的代码,负责发送文件:

 public async void SendFile(string path)
            {
               
                var size = new FileInfo(path).Length;
               
                SendMessage($"FILE:{Path.GetFileName(path)}:{size}");
                Thread.Sleep(200);
                
                using (FileStream fs = new FileStream(path, FileMode.Open))
                {
                    await client.SendAsync(size, fs); // SendAsync is a SSTCP method
                }
                Console.WriteLine("File sent.");
            }

这是服务器 class 中负责接收和保存文件的代码。

private void EventsOnDataReceived(object? sender, DataReceivedEventArgs e)
        {
            Console.WriteLine("New content received: " + e.Data.Length + " Bytes.");
            string message;
            var content = e.Data;
            if (content.Length <= 128) // messages are shorter than 128 bytes
            {
                message = Encoding.UTF8.GetString(content);
                if (message.Contains("FILE:"))
                {
                    _filename = message.Split(":")[1]; // class field containing filename
                    _size = long.Parse(message.Split(":")[2]); // class field con. size
                    return;
                }
            }

            // data bigger than 128 bytes is considered a file (for now)
            using (FileStream fs = new FileStream(_filename, FileMode.Append))
            {
                fs.Write(content);
            }
        }

提前感谢您的帮助。

** 编辑:应@RowanSmith 的要求发布新版本的代码**

SendFile 方法:

public async Task SendFile(string path)
            {
                var size = new FileInfo(path).Length;
                using (FileStream fs = new FileStream(path, FileMode.Open))
                {
                    await client.SendAsync(size, fs);
                }
                Console.WriteLine("File sent.");
            }

服务器接收码:

private void EventsOnDataReceived(object? sender, DataReceivedEventArgs e)
        {
            Console.WriteLine("New content received: " + e.Data.Length + " Bytes.");

            using (FileStream fs = new FileStream("file.png", FileMode.Append))
            {
                fs.Write(e.Data);
            }  
        }

出现此问题是因为您正在接收数据,而您已经在接收数据。事件:

private void EventsOnDataReceived(object? sender, DataReceivedEventArgs e)

在上一个事件仍在将数据写入磁盘时抛出。这是因为磁盘访问比内存访问慢。

要解决此问题,您需要将文件读入内存,然后将文件保存到磁盘。

下面的完整示例有效,而且它之所以有效,是因为内存比从磁盘读取更快。如果你的磁盘因为比内存快,(不太可能),那么这也会中断。

class Program
{

    static async Task Main(string[] args)
    {
        var server = StartServer();
        await Task.Delay(1000);
        _ = StartClient();

        await server;
        Console.WriteLine("Server exited");
    }

    static MemoryStream memoryStream = new();
    static private void EventsOnDataReceived(object sender, DataReceivedEventArgs e)
    {
        memoryStream.Capacity = memoryStream.Capacity + e.Data.Length;
        memoryStream.Write(e.Data);
    }

    static async Task StartServer()
    {
        await Task.Yield();
        var server = new SimpleTcpServer("127.0.0.1:9999");
        server.Events.DataReceived += EventsOnDataReceived;
        server.Start();

        Console.WriteLine("Press any key to quit the server - but wait for the client to finish!");
        Console.ReadKey();

        using FileStream fs = new FileStream("outfile.png", FileMode.Create);
        fs.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
    }

    static async Task StartClient()
    {
        await Task.Yield();
        using (SimpleTcpClient client = new SimpleTcpClient("127.0.0.1:9999"))
        {
            using FileStream fs = new FileStream("startfile.png", FileMode.Open);
            client.Connect();
            client.Send(fs.Length, fs);
            client.Disconnect();
        }
        Console.WriteLine("Client finished");
    }
}