在C#中通过网络发送大文件的好方法?

时间:2009-01-23 02:59:36

标签: c# networking file-transfer

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

可能只有一个客户。也不接受复制到共享目录。所需的唯一通信是客户端说“gimme xyz”和发送它的服务器(以及确保正确发生这种情况所需的一切)。

有什么建议吗?

10 个答案:

答案 0 :(得分:8)

这是一种更简单的方法。使用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

答案 1 :(得分:2)

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

答案 2 :(得分:2)

您可能需要考虑WCF streaming

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

答案 9 :(得分: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方法是样板插槽数据处理,并且会引发一个事件o将收到的数据共享回消费者(在这种情况下是客户端):

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