接收TcpSockets中的文件的函数

huangapple go评论65阅读模式
英文:

Function to receive file in TcpSockets

问题

I heard about FileStream. Is it a good solution?(我听说过FileStream。这是一个好的解决方案吗?)

英文:

I had this function on the server side to receive a file. However the file is too huge and can't read all of it. Is it any other way?

Server:

static void receiveFile(NetworkStream stream, TcpClient client, string op)
        {
            
            Console.WriteLine("Receiving file...");
            string file = receiveMessage(stream, client);

            //Create a new directory to store the files if it doesn't exist
            if (!Directory.Exists("files"))
            {
                Directory.CreateDirectory("files");
            }


            //save the data to a file in the current directory
            File.WriteAllText("./files/" + op.ToLower() + ".txt", file);
            Console.WriteLine("File received and saved to: " + Path.GetFullPath("./files/" + op.ToLower() + ".txt"));
        }

Client

static void sendFile(string filePath, NetworkStream stream)
        {
            //read the file
            byte[] fileData = File.ReadAllBytes(filePath);
            //send the file name
            string fileName = Path.GetFileName(filePath);
            //send the file size
            string fileSize = fileData.Length.ToString();
            //send the file
            stream.Write(fileData, 0, fileData.Length);
        }

I heard about FileStream. Is it a good solution?

答案1

得分: 0

你需要将它流式传输。这是你的问题的答案。我们的代码的第一次迭代可能如下所示:

static void sendFile(string filePath, NetworkStream stream)
{
    const int bufferSize = 8192;
    using var file = File.OpenRead(filePath);
    var buffer = new byte[bufferSize];
    while (true)
    {
        var read = file.Read(buffer, 0, bufferSize);
        if (read == 0)
        {
            break;
        }
        stream.Write(buffer, 0, read);
    }        
}

static void receiveFile(NetworkStream stream, string op)
{
    //如果目录不存在,则创建一个新的目录以存储文件
    if (!Directory.Exists("files"))
    {
        Directory.CreateDirectory("files");
    }

    const int bufferSize = 8192;
    var filePath = Path.Combine("files", op.ToLower() + ".txt");
    using var file = File.OpenWrite(filePath);
    var buffer = new byte[bufferSize];
    while (true)
    {
        var read = stream.Read(buffer, 0, bufferSize);
        if (read == 0)
        {
            break;
        }
        file.Write(buffer, 0, read);
    }
}

这两种方法都以8192字节的块流式传输文件。

因此,sendFile 函数将更多或更少正确地流式传输文件,但 receiveFile 函数永远不会完成,除非客户端关闭套接字。这是一个问题。另一个问题是,如果连续流式传输两个文件,服务器将不知道一个文件何时结束,另一个文件何时开始。因此,为了正确地流式传输,您需要引入一种协议。我们的代码的第二次迭代可能如下所示:

static void sendFile(string filePath, NetworkStream stream)
{
    const int bufferSize = 8192;
    using var file = File.OpenRead(filePath);
    var buffer = new byte[bufferSize];

    // 读取文件大小并将其作为4个字节的固定序列写入套接字
    var length = (int)file.Length;
    var tmpLength = length;
    for (var i = 0; i < 4; i++)
    {
        buffer[i] = (byte)(tmpLength & 0xFF);
        tmpLength >>= 8;
    }
    stream.Write(buffer, 0, 4);

    // 流式传输文件的其余部分
    while (length > 0)
    {
        var read = file.Read(buffer, 0, bufferSize);
        stream.Write(buffer, 0, read);
        length -= read;
    }        
}

static void receiveFile(NetworkStream stream, string op)
{
    //如果目录不存在,则创建一个新的目录以存储文件
    if (!Directory.Exists("files"))
    {
        Directory.CreateDirectory("files");
    }

    const int bufferSize = 8192;
    var filePath = Path.Combine("files", op.ToLower() + ".txt");
    using var file = File.OpenWrite(filePath);
    var buffer = new byte[bufferSize];

    // 读取4个字节的固定序列并从中检索传递的长度
    stream.ReadExactly(buffer, 0, 4);
    var length = 0;
    for (var i = 0; i < 4; i++)
    {
        length <<= 8;
        length += buffer[3-i];
    }

    // 使用检索的长度来流式传输确切数量的字节
    while (length > 0)
    {
        var read = stream.Read(buffer, 0, bufferSize);
        file.Write(buffer, 0, read);
        length -= read;
    }
}

现在这样做要好得多,可能适用于基本需求。仍然有很多地方可以改进,例如身份验证,传递文件元数据如文件名等等。还请注意,代码本身也可以改进,例如整数(反)序列化可以通过 BitConverter 完成。我将保留这个原始示例,以便读者更好地理解正在发生的事情。

从这一点开始,事情只会更加复杂,因此我强烈建议您实际使用像HTTP协议这样广泛使用和设计良好的东西。而且,当您流式传输大文件时,您会遇到其他问题,例如单个TCP连接可能不足够。因为如果连接中断,那么我们需要重新启动流程。这就是设计更复杂的协议,如Torrent协议的原因之一。

英文:

You need to stream it. That is the answer to your question. The first iteration of our code may look like this:

    static void sendFile(string filePath, NetworkStream stream)
    {
        const int bufferSize = 8192;
        using var file = File.OpenRead(filePath);
        var buffer = new byte[bufferSize];
        while (true)
        {
            var read = file.Read(buffer, 0, bufferSize);
            if (read == 0)
            {
                break;
            }
            stream.Write(buffer, 0, read);
        }        
    }

    static void receiveFile(NetworkStream stream, string op)
    {
        //Create a new directory to store the files if it doesn&#39;t exist
        if (!Directory.Exists(&quot;files&quot;))
        {
            Directory.CreateDirectory(&quot;files&quot;);
        }

        const int bufferSize = 8192;
        var filePath = Path.Combine(&quot;files&quot;, op.ToLower() + &quot;.txt&quot;);
        using var file = File.OpenWrite(filePath);
        var buffer = new byte[bufferSize];
        while (true)
        {
            var read = stream.Read(buffer, 0, bufferSize);
            if (read == 0)
            {
                break;
            }
            file.Write(buffer, 0, read);
        }
    }

Both methods stream the file in 8192 chunks.

So sendFile will more or less stream the file correctly, however receiveFile will never finish, unless the client closes the socket. That's one issue. Another issue is that if you stream two files in a row, the server won't know where one ends and the other begins. So in order to stream this properly, you need to introduce some kind of protocol. One idea is to send the file size before sending the actual file. The second iteration of our code may look like this:

    static void sendFile(string filePath, NetworkStream stream)
    {
        const int bufferSize = 8192;
        using var file = File.OpenRead(filePath);
        var buffer = new byte[bufferSize];

        // Read the file size and write it to the socket
        // as a fixed sequence of 4 bytes
        var length = (int)file.Length;
        var tmpLength = length;
        for (var i = 0; i &lt; 4; i++)
        {
            buffer[i] = (byte)(tmpLength &amp; 0xFF);
            tmpLength &gt;&gt;= 8;
        }
        stream.Write(buffer, 0, 4);

        // Stream the rest of the file
        while (length &gt; 0)
        {
            var read = file.Read(buffer, 0, bufferSize);
            stream.Write(buffer, 0, read);
            length -= read;
        }        
    }

    static void receiveFile(NetworkStream stream, string op)
    {
        //Create a new directory to store the files if it doesn&#39;t exist
        if (!Directory.Exists(&quot;files&quot;))
        {
            Directory.CreateDirectory(&quot;files&quot;);
        }

        const int bufferSize = 8192;
        var filePath = Path.Combine(&quot;files&quot;, op.ToLower() + &quot;.txt&quot;);
        using var file = File.OpenWrite(filePath);
        var buffer = new byte[bufferSize];

        // Read the fixed sequence of 4 bytes and 
        // retrieve the passed length from it.
        stream.ReadExactly(buffer, 0, 4);
        var length = 0;
        for (var i = 0; i &lt; 4; i++)
        {
            length &lt;&lt;= 8;
            length += buffer[3-i];
        }

        // Use the retrieved length to stream the exact number
        // of bytes.
        while (length &gt; 0)
        {
            var read = stream.Read(buffer, 0, bufferSize);
            file.Write(buffer, 0, read);
            length -= read;
        }
    }

Now this is a lot better, and may suit basic needs. There's still a lot to improve here, like for example authentication, passing file metadata like filename, etc. etc. Also note that the code itself can be improved, for example integer (de)serialization can be done through BitConverter. I'm leaving this raw example so that the reader understands better what is happening.

Things become only more difficult at this point, so I strongly suggest you actually use something widely used and well designed like HTTP protocol. Also with streaming big files you will encounter other issues, like for example single tcp connection might not be enough. Because if it dies then we need to restart the process. This is one of the reasons more sophisticated protocols like Torrent were designed.

huangapple
  • 本文由 发表于 2023年4月13日 22:55:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76006923.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定