通过tcp连接发送大文件

时间:2011-08-25 05:47:21

标签: c# networking tcp

我需要通过互联网向计算机发送一些大文件。结果我打开了我打算在路由器上使用的端口并转发相应的IP地址。无论如何,让我告诉你我为了实现这一目标而一直在努力的课程。这些类适用于小文件,但有时它们会因大文件而失败。

这是服务器的代码:(它是一个控制台应用程序)

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace ConsoleApplication20
{

    //server
    class Program
    {
        static Server s;
        public static void mm()
        {
            s = new Server("192.168.0.196");
            s.startServer();
            Console.Read();
        }

        static void Main(string[] args)
        {
           // Thread t = new Thread(new ThreadStart(mm));
           // t.Start();
            mm();
            Console.Read();
            s.disconnect();
        }


    }



    class MyTCP
    {
        protected const int MaxChunkSize = 4096;

        protected Int32 port { get; set; }
        protected string serverIP { get; set; }
        protected TcpClient client { get; set; }
        protected static NetworkStream stream { get; set; }

        protected void sendData(NetworkStream stream, Byte[] data)
        {
            // Send the message to the connected TcpServer. 
            stream.Write(data, 0, data.Length);
        }

        protected String receiveData(NetworkStream stream)
        {
            // Buffer to store the response bytes.
            Byte[] data = new Byte[MaxChunkSize];

            // String to store the response ASCII representation.
            String responseData = String.Empty;

            // Read the first batch of the TcpServer response bytes.
            Int32 bytes = stream.Read(data, 0, data.Length);
            responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
            Console.WriteLine("Received: {0}", responseData);

            return responseData;
        }

        protected static Byte[] textToBytes(string text)
        {
            return System.Text.Encoding.ASCII.GetBytes(text);
        }

        public virtual void disconnect() { }

        public bool isServerConected { get { return client.Connected; } }
    }

    [Serializable]
    public class FileProperties 
    {
        public string FileName { get; set; }
        public string DestPath { get; set; }
        public double FileSize { get; set; }

        public FileAttributes fileAttributes { get; set; }
        public System.Security.AccessControl.FileSecurity FileSecurity { get; set; }
        public DateTime creationTime { get; set; }
        public DateTime lastAccessTime { get; set; }
        public DateTime lastWriteTime { get; set; }   
    }

    class Server: MyTCP
    {
        private System.IO.FileStream _FileStream;
        private static TcpListener server;
        private static bool disconect;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="localAddr">The ip address of the server</param>
        /// <param name="port">on what port the server going to be listening to?</param>
        /// <param name="autoStartServer">start listening for connections now? you may call the startserver() method latter...</param>
        public Server(string localAddr, Int32 port = 13000, bool autoStartServer = false)
        {
            this.port = port;
            this.serverIP = localAddr;

            if (autoStartServer)
                start();
        }

        /// <summary>
        /// Start listening for connections
        /// </summary>
        public void startServer()
        {
            start();
        }

        public override void disconnect()
        {
            // Close everything.
            stream.Close();
            client.Close();
            server.Stop();
            disconect = true;
        }


        void start()
        {
            server = null;

            try
            {
                // TcpListener server = new TcpListener(port);
                server = new TcpListener(IPAddress.Parse(serverIP), port);

                // Start listening for client requests.
                server.Start();

                // Buffer for reading data
                Byte[] bytes = new Byte[MaxChunkSize];
                String data = null;

                // Enter the listening loop.
                while (disconect==false)
                {
                    Console.Write("Waiting for a connection... ");

                    // Perform a blocking call to accept requests.
                    // You could also user server.AcceptSocket() here.
                    client = server.AcceptTcpClient();
                    Console.WriteLine("Connected!");




                    // Get a stream object for reading and writing
                    stream = client.GetStream();



                    int i;
                    try
                    {
                        // Loop to receive all the data sent by the client.
                        while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                        {


                            // Translate data bytes to a ASCII string.
                            data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                            Console.WriteLine("Received: {0}", data);

                            if (data.ToUpper().Contains("<sendFile>".ToUpper()))
                            {
                                receiveFile(bytes);
                            }



                            continue;


                        }
                    }
                    catch { }

                    // Shutdown and end connection
                    client.Close();
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            finally
            {
                // Stop listening for new clients.
                server.Stop();
            }


            Console.WriteLine("\nHit enter to continue...");
            Console.Read();
        }


        void receiveFile(Byte[] bytes)
        {
            // send 1
            sendData(stream, textToBytes("<1>"));

            // receive 2
            int length = stream.Read(bytes, 0, bytes.Length);
            byte[] tempA = new byte[length];
            for (int k = 0; k < length; k++)
                tempA[k] = bytes[k];

            Stream ms = new MemoryStream(tempA);
            FileProperties p = new FileProperties();
            System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(p.GetType());

            try
            {
                p = (FileProperties)x.Deserialize(ms);

                if (Directory.Exists(p.DestPath))
                {
                    //send 3
                    sendData(stream, textToBytes("<3>"));
                }
                else
                {
                    //send 3
                    sendData(stream, textToBytes("<no>"));
                    return;
                }
            }
            catch
            {
                //send 3
                sendData(stream, textToBytes("<no>"));
                return;
            }



            int i;

            string temp = Path.Combine(new string[]{ p.DestPath, p.FileName + ".temp"});

            _FileStream = new System.IO.FileStream(temp, System.IO.FileMode.Create, System.IO.FileAccess.Write);

                while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                {
                    if (i == 11 & System.Text.Encoding.ASCII.GetString(bytes, 0, i).ToUpper().Equals("</sendFile>".ToUpper()))
                    {
                        _FileStream.Close();

                        Console.WriteLine("D!");

                        File.SetAttributes(temp, p.fileAttributes);
                        File.SetAccessControl(temp, p.FileSecurity);
                        File.SetCreationTime(temp, p.creationTime);
                        File.SetLastAccessTime(temp, p.lastAccessTime);
                        File.SetLastWriteTime(temp, p.lastWriteTime);

                        if(File.Exists(temp.Substring(0, temp.Length - 4)))
                            File.Delete(temp.Substring(0, temp.Length - 4));

                        File.Move(temp, temp.Substring(0, temp.Length - 4));


                        //sendData(stream, textToBytes("<done>"));

                        Console.WriteLine("Done!");

                        return;
                    }
                    _FileStream.Write(bytes, 0, i);

                }



            return;

        }
    }
}

我客户的代码是:

using System;
using System.Net.Sockets;
using System.Windows;
using System.IO;


namespace WpfApplication23sdfd
{

    [Serializable]
    public class FileProperties 
    {
        public string FileName { get; set; }
        public string DestPath { get; set; }
        public double FileSize { get; set; }

        public FileAttributes fileAttributes { get; set; }
        public System.Security.AccessControl.FileSecurity FileSecurity { get; set; }
        public DateTime creationTime { get; set; }
        public DateTime lastAccessTime { get; set; }
        public DateTime lastWriteTime { get; set; }
    }

    abstract class MyTCP
    {
        protected const int MaxChunkSize = 4096;

        protected Int32 port { get; set; }
        protected string serverIP { get; set; }
        protected TcpClient client { get; set; }
        protected static NetworkStream stream { get; set; }

        protected void sendData(NetworkStream stream, Byte[] data)
        {

            // Send the message to the connected TcpServer. 
            stream.Write(data, 0, data.Length);

            // Receive the TcpServer.response.
        }

        protected String receiveData(NetworkStream stream)
        {
            // Buffer to store the response bytes.
            Byte[] data = new Byte[MaxChunkSize];

            // String to store the response ASCII representation.
            String responseData = String.Empty;

            // Read the first batch of the TcpServer response bytes.
            Int32 bytes = stream.Read(data, 0, data.Length);
            responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
            Console.WriteLine("Received: {0}", responseData);

            return responseData;
        }

        protected static Byte[] textToBytes(string text)
        {
            return System.Text.Encoding.ASCII.GetBytes(text);
        }

        public virtual void disconnect() { }

        public bool isServerConected { get { return client.Connected; } }
    }

    //client
    class Client: MyTCP
    {



        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="serverIP">the ip address of the server</param>
        /// <param name="port">through what port is the connection going to be established</param>
        public Client(string serverIP, Int32 port = 13000, bool autoConnect = false)
        {
            this.port = port;
            this.serverIP = serverIP;

            if (autoConnect)
                connect();
        }


        public bool connect()
        {
            Byte[] data = System.Text.Encoding.ASCII.GetBytes("connect");

            // Create a TcpClient.
            // Note, for this client to work you need to have a TcpServer 
            // connected to the same address as specified by the server, port
            // combination.
            try
            {
                client = new TcpClient(serverIP, port);

                // Get a client stream for reading and writing.
                //  Stream stream = client.GetStream();
                stream = client.GetStream();

                return true;


            }
            catch
            {
                return false;
            }

        }

        public override void disconnect()
        {
            // Close everything.
            stream.Close();
            client.Close();
        }

        static void ConnectOld(String server, Byte[] data)
        {
            try
            {
                // Create a TcpClient.
                // Note, for this client to work you need to have a TcpServer 
                // connected to the same address as specified by the server, port
                // combination.
                Int32 port = 13000;
                TcpClient client = new TcpClient(server, port);



                // Get a client stream for reading and writing.
                //  Stream stream = client.GetStream();

                NetworkStream stream = client.GetStream();

                // Send the message to the connected TcpServer. 
                stream.Write(data, 0, data.Length);



                // Receive the TcpServer.response.

                // Buffer to store the response bytes.
                data = new Byte[256];

                // String to store the response ASCII representation.
                String responseData = String.Empty;

                // Read the first batch of the TcpServer response bytes.
                Int32 bytes = stream.Read(data, 0, data.Length);
                responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
                Console.WriteLine("Received: {0}", responseData);

                // Close everything.
                stream.Close();
                client.Close();
            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine("ArgumentNullException: {0}", e);
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }

            Console.WriteLine("\n Press Enter to continue...");
            Console.Read();
        }

        public void sendFile(string file, string destPath = "c:\\")
        {

            //let server know what you are going to be doing...
            sendData(stream, textToBytes("<sendFile>"));

            FileProperties p = new FileProperties { 
                creationTime = File.GetCreationTime(file), 
                fileAttributes = File.GetAttributes(file), 
                FileSecurity = File.GetAccessControl(file), 
                lastAccessTime = File.GetLastAccessTime(file), 
                lastWriteTime = File.GetLastWriteTime(file),
                 DestPath = destPath,
                 FileName = Path.GetFileName(file)
            };


            // receive 1
            if (!receiveData(stream).ToUpper().Contains("<1>".ToUpper()))
            {
                MessageBox.Show("Error comunicating with server");
                return;
            }

            // send object p to server
            System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(p.GetType());
            x.Serialize(stream, p); // send 2

            //recieve 3
            if (!receiveData(stream).ToUpper().Contains("<3>".ToUpper()))
            {
                MessageBox.Show("Error incorrect parameters sent to server");
                return;
            }


            System.IO.FileStream streamFile = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read);



            while (true)
            {
                byte[] chunk = new byte[MaxChunkSize];

                int index = 0;
                // There are various different ways of structuring this bit of code.
                // Fundamentally we're trying to keep reading in to our chunk until
                // either we reach the end of the stream, or we've read everything we need.
                while (index < chunk.Length)
                {
                    int bytesRead = streamFile.Read(chunk, index, chunk.Length - index);


                    if (bytesRead == 0)
                    {
                        break;
                    }
                    if (bytesRead < MaxChunkSize)
                    {
                        byte[] temp = new byte[bytesRead];

                        for (var i = 0; i < bytesRead; i++)
                            temp[i] = chunk[i];

                        chunk = temp;
                    }


                    index += bytesRead;
                }
                if (index != 0) // Our previous chunk may have been the last one
                {
                    sendData(stream,chunk); // index is the number of bytes in the chunk
                }
                if (index != chunk.Length) // We didn't read a full chunk: we're done
                {

                    sendData(stream, textToBytes("</sendFile>".ToUpper()));

                    //receiveData(stream);//wait recall missing to check results

                    return;
                }
            }

        }

    }
}

我实例化客户端类的方法是将服务器的IP地址提供为:

Client c = new Client("192.168.0.196");
c.sendFile(@"A:\Users\Tono\Desktop\a.mp4");

在执行该代码之前必须先运行服务器。

我不知道为什么使用套接字通过互联网发送文件这么复杂。我不知道WCF这就是为什么我花了很多时间创建这个类的原因。也许已经有一些内置类可以让我通过互联网将文件发送到另一台计算机。我只知道网络的基础知识,如果我能用一个简单的类来做它会很好。我不明白为什么我的课程不能一直工作?如果我增加缓冲区大小,我的课程会更有效吗?在发送更多字节之前,我是否必须等待或暂停我的程序?如果有人能告诉我这门课有什么问题,那将会很好。它们适用于小文件,但有大文件有时它不起作用......

4 个答案:

答案 0 :(得分:9)

我可以立即看到一些问题。可能导致您的程序仅在某些时间工作的一个事实是,通过TCP发送并不能保证每个send都会在另一侧产生相同大小的receive。 / p>

您的协议似乎假设它会,因为您正在等待</sendFile>的正好读取11个字节,而它可以在多个单独的读取中接收。例如:“[文件数据......]”。如果发生这种情况,您的代码将无法正确完成。

值得注意的是,ASCII编码是7位,因此二进制文件(例如MP4)将被错误地接收(即使您修复了上述内容)。如果是二进制数据,则不应尝试将其转换为字符串,而应直接从byte[]将其写入文件。

如果您希望限制此路由(而不是使用其他答案中提到的许多现有文件传输系统),那么您可能还需要更改协议,以便不使用{{1}分隔文件你最初发送文件的长度,这将允许你发送可能包含这些特殊标签之一的文件。

答案 1 :(得分:5)

通过套接字发送文件/目录并非易事。我建议使用一些文件传输库(通过套接字,或者更高级别的协议,即rsync,ftp,http等),而不是尝试从头开始编写代码。

略过代码后 - 尝试发送一个包含一些统一内容的大文件(填充'0'或其他内容)。如果它会通过 - 你的xml的东西不起作用。

答案 2 :(得分:5)

这是我发送大文件的代码。 一些提示:

  • 检查缓冲区大小。如果它太大就会失败。
  • 插座标志。部分旗帜效果最佳。
  • 由于转移时间,需要套接字超时。

客户:

string IPAddress = "";
        int Port = 500;

        string Filename = @"C:\Users\Ben\Desktop\TT.zip";


        int bufferSize = 1024;
        byte[] buffer = null;
        byte[] header = null;


        FileStream fs = new FileStream(Filename, FileMode.Open);
        bool read = true;

        int bufferCount = Convert.ToInt32(Math.Ceiling((double)fs.Length / (double)bufferSize));



        TcpClient tcpClient = new TcpClient(IPAddress, Port);
        tcpClient.SendTimeout = 600000;
        tcpClient.ReceiveTimeout = 600000;

        string headerStr = "Content-length:" + fs.Length.ToString() + "\r\nFilename:" + @"C:\Users\Administrator\Desktop\" + "test.zip\r\n";
        header = new byte[bufferSize];
        Array.Copy(Encoding.ASCII.GetBytes(headerStr), header, Encoding.ASCII.GetBytes(headerStr).Length);

        tcpClient.Client.Send(header);

        for (int i = 0; i < bufferCount; i++)
        {
            buffer = new byte[bufferSize];
            int size = fs.Read(buffer, 0, bufferSize);

            tcpClient.Client.Send(buffer,size,SocketFlags.Partial);

        }

        tcpClient.Client.Close();

        fs.Close();

服务器:

int Port = 500;

        TcpListener listener = new TcpListener(IPAddress.Any, Port);
        listener.Start();


        Socket socket = listener.AcceptSocket();

        int bufferSize = 1024;
        byte[] buffer = null;
        byte[] header = null;
        string headerStr = "";
        string filename = "";
        int filesize = 0;


        header = new byte[bufferSize];

        socket.Receive(header);

        headerStr = Encoding.ASCII.GetString(header);


        string[] splitted = headerStr.Split(new string[] { "\r\n" }, StringSplitOptions.None);
        Dictionary<string, string> headers = new Dictionary<string, string>();
        foreach (string s in splitted)
        {
            if (s.Contains(":"))
            {
                headers.Add(s.Substring(0,s.IndexOf(":")), s.Substring(s.IndexOf(":") + 1));
            }

        }
        //Get filesize from header
        filesize = Convert.ToInt32(headers["Content-length"]);
        //Get filename from header
        filename = headers["Filename"];

        int bufferCount = Convert.ToInt32(Math.Ceiling((double)filesize / (double)bufferSize));


        FileStream fs = new FileStream(filename, FileMode.OpenOrCreate);

        while(filesize > 0)
        {
            buffer = new byte[bufferSize];

            int size = socket.Receive(buffer,SocketFlags.Partial);

            fs.Write(buffer,0,size);

            filesize -= size;
        }


        fs.Close();

希望这会对某人有所帮助。

答案 3 :(得分:2)

基本上你正在做的是正确的。我可以建议改进的要点是将MaxChunkSize设置为更大的值,高达65000.这将使套接字代码能够处理任何碎片,这将比您将通过任何碎片进行分割更有效。自己。

此外,您知道发送大文件需要一些时间。在100 Mbit LAN中,带宽(理论上)为每秒12.5 MB。因此,发送700 MByte文件仍需要56秒。当然,实际吞吐量取决于网络中的许多因素以及所涉及的计算机和网络硬件,因此预计会更慢。

最后,Wireshark(或任何其他嗅探器程序)是网络程序员工具箱中最基本和最宝贵的工具。在服务器和客户端上使用它来查看TCP数据包的传输方式,看看是否可以识别慢速传输的任何原因。