【问题标题】:Good way to send a large file over a network in C#?在 C# 中通过网络发送大文件的好方法?
【发布时间】:2010-10-03 01:43:30
【问题描述】:

我正在尝试构建一个应用程序,该应用程序可以从网络中另一台机器上运行的服务请求文件。这些文件可能相当大(有时 500mb +)。我正在考虑通过 TCP 发送它,但我担心它可能需要将整个文件存储在内存中。

可能只有一个客户。复制到共享目录也是不可接受的。唯一需要的通信是客户端说“给我 xyz”,服务器发送它(以及确保正确发生的任何事情)。

有什么建议吗?

【问题讨论】:

    标签: c# networking file-transfer


    【解决方案1】:

    我个人会选择平衡速度、可靠性和经济代码的东西,所以我会基于 TCP 网络流。代码的客户端如下所示:

    internal class Client
    {
        private FileStream _fs;     
        private long _expectedLength;
    
        public void GetFileFromServer(string localFilename)
        {            
            if (File.Exists(localFilename))
                File.Delete(localFilename);
    
            _fs = new FileStream(localFilename, FileMode.Append);
    
            var ipEndpointServer = new IPEndPoint(IPAddress.Parse({serverIp}), {serverPort});
    
            // an object that wraps tcp client
            var client = new TcpClientWrapper(ipEndpointServer, "");
            client.DataReceived += DataReceived;
        }
    
        private void DataReceived(object sender, DataReceivedEventArgs e)
        {
            var data = e.Data;
    
            // first packet starts with 4 bytes dedicated to the length of the file
            if (_expectedLength == 0)
            {
                var headerBytes = new byte[4];
                Array.Copy(e.Data, 0, headerBytes, 0, 4);
                _expectedLength = BitConverter.ToInt32(headerBytes, 0);
                data = new byte[e.Data.Length - 4];
                Array.Copy(e.Data, 4, data, 0, data.Length);
            }
    
            _fs.WriteAsync(e.Data, 0, e.Data.Length);
    
            if (_fs.Length >= _expectedLength)
            {                                
                // transfer has finished
            }
        }
    }
    

    然后有一个服务器类来提供文件。请注意,整个文件并未加载到内存中,而是从FileStream 中分块读取。

    internal class Server
    {
        private TcpServer _tcpServer;
        private NetworkStream _stream;        
    
        public void StartServer()
        {
            // fire up a simple Tcp server
            _tcpServer = new TcpServer({serverPort}, "test");
            _tcpServer.ClientConnected += ClientConnected;
        }
    
        private void ClientConnected(object sender, TcpClientConnectedEventArgs e)
        {            
            // an incoming client has been detected ... send the file to that client!
            _stream = e.Client.GetStream();
            SendFileToClient({pathToFile});
        }
    
        private void SendFileToClient(string pathToFile)
        {
            // open the file as a stream and send in chunks
            using (var fs = new FileStream(pathToFile, FileMode.Open))
            {
                // send header which is file length
                var headerBytes = new byte[4];
                Buffer.BlockCopy(BitConverter.GetBytes(fs.Length + 4), 0, headerBytes, 0, 4);
                _stream.Write(headerBytes, 0, 4);
    
                // send file in block sizes of your choosing
                var buffer = new byte[100000];
                int bytesRead = 0;
                while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
                {
                    _stream.Write(buffer, 0, bytesRead);
                }
                _stream.Flush();
            }
        }
    }
    

    TcpClientWrapper 几乎是带有System.Net.Sockets.TcpClient 对象和底层NetworkStream 对象的样板代码。我也不需要发布这个,但只是为了提供一些指示,构造将包含如下内容:

    _tcp = new Net.TcpClient();
    _tcp.Connect(remoteEp);
    _stream = _tcp.GetStream();
    _stream.BeginRead(_receivedData, 0, _receivedData.Length, DataReceivedAsync, null);
    

    DataReceivedAsync 方法是样板套接字数据处理,会引发一个事件,将接收到的数据共享回消费者(在本例中为客户端):

    private void DataReceivedAsync(IAsyncResult ar)
    {
        var receivedBytes = _stream.EndRead(ar);
    
        if (receivedBytes > 0)
        {
            var data = new byte[receivedBytes];
            Array.Copy(_receivedData, 0, data, 0, receivedBytes);
            DataReceived?.Invoke(this, new DataReceivedEventArgs(data));
    
            _receivedData = new byte[ReceiveBufferSize];
            _stream.BeginRead(_receivedData, 0, _receivedData.Length, DataReceivedAsync, null);
        }       
    }   
    

    将数据从包装器发送回客户端的事件:

    public EventHandler<DataReceivedEventArgs> DataReceived;
    public class DataReceivedEventArgs : EventArgs
    {
        public DataReceivedEventArgs(byte[] data) { Data = data; }
        public byte[] Data { get; }
    }
    

    【讨论】:

    • 这是一个很好的解决方案
    【解决方案2】:

    小心使用 BITS。这是一个非常好的协议,但不是 Windows 更新程序的关键部分。我们发现几乎没有我们的企业客户允许将 BITS 更新到他们的机器上。因此我们无法构建依赖它的应用程序。

    【讨论】:

      【解决方案3】:

      这篇文章可能会对您有所帮助。它是关于在 .NET 中发送大文件。 检查链接:

      http://codetechnic.blogspot.com/2009/02/sending-large-files-over-tcpip.html

      【讨论】:

        【解决方案4】:

        通过开源 edtFTPnet 库使用 FTP。快速简单。

        【讨论】:

          【解决方案5】:

          如果文件实际存在于机器上,为什么不将它们放在一个文件夹中,将该文件夹设为 IIS 中的虚拟目录,并使用基于内容的路由和/或 URL 重写将请求路由到它们。

          【讨论】:

            【解决方案6】:

            这是一个更简单的方法。使用 BITS(后台智能传输服务)。它已经内置在 WinXP 和 Vista 中。它基本上是驱动 Windows 更新的原因。

            http://blogs.msdn.com/powershell/archive/2009/01/11/transferring-large-files-using-bits.aspx

            http://blogs.msdn.com/jamesfi/archive/2006/12/23/how-to-use-bits-to-transfer-files.aspx

            这是某人编写的一个很好的托管 BITS 包装器以及如何使用它。

            http://www.codeproject.com/KB/cs/Managed_BITS.aspx

            【讨论】:

              【解决方案7】:

              您可能需要考虑WCF streaming

              【讨论】:

                【解决方案8】:

                如果 FTP 是一个选项,那么为了简单起见,我会选择它。否则,您将进入 TCP/IP 套接字编程的世界。

                【讨论】:

                  【解决方案9】:

                  您可以在 .NET 中使用套接字来传输文件和数据。

                  【讨论】:

                    【解决方案10】:

                    使用TransmitFile(这是一个 Win32 函数;也许它也是 .NET 库的一种方法)。

                    【讨论】:

                      猜你喜欢
                      • 2015-06-23
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-08-21
                      • 2010-10-17
                      • 1970-01-01
                      相关资源
                      最近更新 更多