【问题标题】:C# SuperSimpleTCP: Received file is partially corruptedC# SuperSimpleTCP:接收到的文件部分损坏
【发布时间】:2021-11-16 10:00:26
【问题描述】:

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

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

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

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

但是,上述内容都没有改变文件的行为方式。

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

 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.");
            }

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

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);
            }  
        }

【问题讨论】:

  • 您如何发出传输完成的信号?您如何向服务器发送文件长度信号?
  • @RowanSmith 他们通过SendMessage($"FILE:{Path.GetFileName(path)}:{size}");Thread.Sleep( 200 ); 之前发送尺寸。至少我认为SendMessage 是这样做的。
  • 除非您设置 NoDelay,否则 200 毫秒可能不足以等待。即便如此,这也是一种糟糕的做法。创建适当的成帧协议来承载流量的报头信息。认识到 TCP 是一个流,它看起来就像磁盘上的一个文件,一直读取到没有数据为止,然后继续尝试读取特定的超时时间,当超时到期时,将其视为磁盘上的文件磁盘损坏并被提前截断。
  • 使用 async Task 代替 async void。使用await Task.Delay 而不是 Thread.Sleep() - 但实际上你不应该拖延。
  • @RowanSmith 呈现的版本被剥夺了传输完成信号功能。这是因为 - 正如我在帖子中提到的 - 它对文件没有影响,所以我删除了它并简化了代码。现在服务器接收到它保存的那一刻包(行:使用(FileStream fs = new FileStream(_filename,FileMode.Append)))我应该在我的帖子中对此进行说明,对此感到抱歉。

标签: c# tcp


【解决方案1】:

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

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");
    }
}

【讨论】:

  • 1.我不是。我对 SSTCP 有信心,它甚至没有给我检查的方法,所以我想它自己处理它。 2. 它没有。它也不会退出,只是在单独的线程中运行并等到我杀死应用程序。我添加了 Task.Delay 和 Flush - 没有变化。
  • 但是我观察到一些有趣的事情:当打开原始文件和损坏的文件并使用记事本++相互比较时,似乎数据在那里,但不知何故......转移了?屏幕截图:imgur.com/a/uxxuf5J 深灰色字段表示代码相同,浅灰色表示“空白空间”,蓝色表示代码不同的地方。
  • 您确定在追加文件之前删除该文件吗?
  • 好的,我知道你的代码有什么问题了。问题是当您仍处于写入模式时会引发事件。我猜你是在一台电脑上做这个吗?如果您通过网络执行此操作,那么它可能会起作用。文件已损坏,因为您在打开文件以进行写入时接收到更多数据。当您首先将文件读入内存时,它可以工作,因为访问磁盘子系统没有延迟。
  • 好的,我上面写了一个完整版,可以 100% 工作。如果您想这样做,您需要先将文件保存到内存中,然后再将其写入磁盘。如果您的文件对于您的内存来说太大了,那么您需要创建一个框架协议以位于 SuperSimpleTcp 之上
猜你喜欢
  • 1970-01-01
  • 2023-03-07
  • 2011-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多