在C#中发送大文件的好方法是什么?

7
我正在尝试构建一个应用程序,可以从网络上运行的另一台计算机的服务请求文件。这些文件可能相当大(有时超过500mb)。我考虑通过TCP发送它,但担心这可能需要将整个文件存储在内存中。
可能只会有一个客户端。复制到共享目录也不可接受。唯一需要通信的是客户端说“给我xyz”,服务器就会发送它(以及确保正确执行所需的任何内容)。
有什么建议吗?
10个回答

8
这里有一种更简单的方式,使用 BITS(后台智能传输服务)。它已经内置于 WinXP 和 Vista 中。基本上就是驱动 Windows 更新的东西。
这里有一个不错的托管 BITS 包装器和如何使用它的链接:http://www.codeproject.com/KB/cs/Managed_BITS.aspx
此外,以下链接提供了关于 BITS 的更多信息和使用指南:http://blogs.msdn.com/powershell/archive/2009/01/11/transferring-large-files-using-bits.aspxhttp://blogs.msdn.com/jamesfi/archive/2006/12/23/how-to-use-bits-to-transfer-files.aspx

2
你可以使用.NET中的套接字来传输文件和数据。

2

2

2

请注意 BITS 协议。它是一种非常好的协议,但不是 Windows 更新程序的关键部分。我们发现,我们的公司客户中几乎没有人允许 BITS 更新到他们的计算机上;因此,我们无法构建一个依赖于它的应用程序。


1

如果FTP是一种选择的话,基于简单考虑,我会选择FTP。否则你需要了解TCP/IP套接字编程。


1

使用开源edtFTPnet库通过FTP进行操作。快速简便。


0

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


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

0

就我个人而言,我会选择平衡速度、可靠性和经济性的代码,因此我会以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; }
}

这是一个很棒的解决方案。 - TheLegendaryCopyCoder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接