zoukankan      html  css  js  c++  java
  • 文件传输

    文件传输

     

    前面的案例都是传输字符串.另一种常见的情况,就是在服务端和client之间传输文件.

     

    计入,client显示了一个菜单:当输入S1,S2S3,分别向服务端发送文件Client01.jpg,Client02.jpg,Client02.jpg;当输入R1,R2R3,分别从服务端接受文件Server01.jpg,Server02.jpg,Server03.jpg.那么,怎样完毕这项功能呢?

     

    方法1:类似FTP协议,服务端监听两个port:一个称为控制port,用于接受各种命令字符串(仅仅是接受或发送文件);一个称为数据port,用于传输实际数据,就是发送和接收文件

     

    方法2:服务端仅仅监听一个port,用于接受来自client的命令字符串,称为控制port.client在发出命令字符串之前,先开辟一个本地port专用于收发文件,然后向服务端发送命令字符串,当中包括了client专用于收发数据的port号.服务端接收到请求之后,连接至client专用于收发文件的port,然后数据传输,结束后关闭连接.

     

    如今仅仅关注两种方式中的数据port,当用法1的时候,服务端的数据port须要同一时候为多个client的多次请求服务,因此须要採用异步方式;用法2,服务端与client在每次数据传输时都会又一次建立新的连接,在传输完毕后马上关闭,因此在数据port上传输文件时不须要採用异步方式.可是在控制port仍然须要使用异步方式.

     

    咱们用法2来实现文件的接受.

     

     

    订立协议

     

    1.发送文件

     

    先看一下发送文件的情况:假设要将Client01.jpg由client发往服务端,能够归纳为下面几个步骤:

    (1).client对port进行监听,如果port号为8005.

    (2).如果客户输入了S1,则发送以下的控制字符串到服务端:[file=Client01.jpg,mode=send.port=8005].

    (3)服务端收到以后,依据clientIP和port号与该client建立连接

    (4)client向网络流羞辱数据,開始发送文件.

    (5)传送完成之后client和服务端关闭数据port的连接.

     

    类似之前的规则,订立的公布文件协议能够为:[file=Client01.jpg,mode=send,port=8005].可是,因为他是一个普通的字符串,在前面我们採用了正則表達式的方法来获得当中的有效值,但还有更好地办法----使用XML.对于上面的语句,我们能够写成这种XML:

     

    <protocol><file name=”Client01.jpg” mode=senf,port=”8005” /></protocol>


    这样处理起来就会方便多了,由于处理XML格式有更丰富的类型支持.接下来看一下接收文件的流程及其协议.

     

    2.接收文件

     

    接收文件与发送文件实际上全然类似,差别仅仅是:当发送文件时,由client向网络流写入数据;当接收文件时,由服务端想网络流介入数据.接受文件的过程例如以下:

    (1).client对port进行监听,如果port号为8006.

    (2).如果client输入了R1,则发送控制字符串:<protocol><file name=”Server01.jpg” mode=”receive” port=”8006”/></protocol>到服务端.

    (3).服务端接收到以后,依据clientIP和port号与该client建立连接.

    (4).服务端向网络流写入数据,開始发送文件.

    (5).传送完成后client和服务端关闭数据port的连接.

     

     

    协议处理类的实现

     

    在開始编写实际的服务端client代码之前,首先要编写处理协议的类,这个类须要两个功能:一是方便获取完整的协议信息,由于前面说过,服务端可能将client的多次独立请求拆分或合并.比方,client连续发送了两条控制信息到服务端,而服务端将它们合并了,则须要先拆开再分别处理.二是方便获取节点信息,由于协议是XML格式,所以还须要一个类专门对XML进行处理,获得节点的值.

     

    1.ProtocolHandler辅助类

     

    先看下ProtocolHandler,它与前面说过的ResquestHandler作用同样.须要注意的是必须将它声明为实例的,而非静态的,这是由于每一个TcpClient都须要相应一个ProtocolHandler,它内部维护的partialProtocol不能共享,在协议发送不完整的情况下,这个变量用于暂时保存被截断的字符串.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;
     
    namespace Server
    {
        public class ProtocolHandler
        {
            private string partialProtocol;//保存不完整的协议
            public ProtocolHandler()
            {
                partialProtocol = "";
            }
            public string[] GetProtocol(string input)
            {
                return GetProtocol(input,null);
            }
            //获得协议
            private string[] GetProtocol(string input, List<string>outputList)
            {
                if (outputList==null)
                {
                    outputList = new List<string>();
                }
                if (string.IsNullOrEmpty(input))
                {
                    return outputList.ToArray();
                }
                if (!string.IsNullOrEmpty(partialProtocol))
                {
                    input = partialProtocol + input;
                }
     
                string pattern = "(^<protocol>.*?

    </protocol>)"; //假设有匹配,说明已经找到了,是完整的协议 if (Regex.IsMatch(input,pattern)) { //获取匹配的值 string match = Regex.Match(input,pattern).Groups[0].Value; outputList.Add(match); partialProtocol = ""; //缩短input的长度 input = input.Substring(match.Length); //递归调用 GetProtocol(input,outputList); } else { //假设不匹配,说明协议长度不够 //那么先缓存,然后等待下一次请求 partialProtocol = input; } return outputList.ToArray(); } } }



    由于这个类不是重点,这里贴下代码就够了.

     

    2.FileRequestType枚举类型和FileProtocol结构

     

    由于XML是以字符串的形式进行传输,为了方便使用,最好构建一个强类型对它们进行操作,这样会方便非常多.首先能够定义FileRequestMode枚举类型,用来表示是发送文件还是接受文件:

        public enum FileRequestMode
        {
            Send=0,
            Receive
        }


    接下来再定义一个FileProtocol结构,用来为整个协议字符串提供强类型的訪问,注意这里覆盖了基类的ToString()方法,这样在client就不须要再手工去编写XML,仅仅要在FileProtocol结构值上调用ToString()即可了:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace Server
    {
        public struct FileProtocol
        {
            private readonly FileRequestMode mode;
            private readonly int port;
            private readonly string fileName;
     
            public FileProtocol(FileRequestMode mode, int port, string fileName)
            {
                this.mode = mode;
                this.port = port;
                this.fileName = fileName;
            }
            public FileRequestMode Mode { get { return mode; } }
     
            public int Port
            {
                get
                {
                    return port;
                }
            }
     
            public string FileName
            {
                get
                {
                    return fileName;
                }
            }
     
            public override string ToString()
            {
                return string.Format("<protocol><file name="{0}" mode="{1}" port="{2}"/></protocol>",fileName,mode,port);
            }
        }
    }


     

    3.ProtocolHelper辅助类

     

    这个类专用于将XML格式的协议映射为上面定义的强类型对象,这里没有增加try/catch异常处理,由于协议对用户来说是不可见的,而client应该总是发送正确的协议:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml;
     
    namespace Server
    {
        public class ProtocolHelper
        {
            private XmlNode fileNode;
            private XmlNode root;
     
            public ProtocolHelper(string protocol)
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(protocol);
                root = doc.DocumentElement;
                fileNode = root.SelectSingleNode("file");
            }
     
            //此时的protocol已定位单条完整的protocol
            private FileRequestMode GetFileMode()
            {
                string mode = fileNode.Attributes["mode"].Value;
                mode = mode.ToLower();
                if (mode=="send")
                {
                    return FileRequestMode.Send;
                }
                else
                {
                    return FileRequestMode.Receive;
                }
            }
     
            //获取单条协议包括的信息
            public FileProtocol GetProtocol()
            {
                FileRequestMode mode = GetFileMode();
                string fileName = "";
                int port = 0;
                fileName = fileNode.Attributes["name"].Value;
                port = Convert.ToInt32(fileNode.Attributes["port"].Value);
     
                return new FileProtocol(mode,port,fileName);
            }
        }
    }


    关于XML部分的内容,我们会具体解说.

     

     

    client发送文件

     

    我们还是讲一个问题分为两部分来处理:先是client发送文件,然后是服务端接收文件.

     

    1.client的实现

     

    不着急实现clientS1,R1等用户菜单,首先完毕发送文件这一功能为前面的Client类再加入一个SendFile()方法.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading.Tasks;
    using System.Net;
    using System.Threading;
    using System.IO;
    using Server;
    namespace Client
    {
        public class Client
        {
            private const int BufferSize = 8192;
            private byte[] buffer;
            private TcpClient client;
            private NetworkStream streamToServer;
     
            public Client()
            {
                try
                {
                    client = new TcpClient();
                    client.Connect("192.168.3.19",8500);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    throw;
                }
                buffer = new byte[BufferSize];
     
                //打印连接到的server信息
                Console.WriteLine("Server Connected! Local: {0}-->Server:{1}",client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
                streamToServer = client.GetStream();
            }
     
            //发送消息到server
            public void SendMessage(string msg)
            {
                byte[] temp = Encoding.Unicode.GetBytes(msg);//获得缓存
                try
                {
                    streamToServer.Write(temo,0,temp.Length);
                    Console.WriteLine("Sent: {0}", msg);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    throw;
                }
            }
     
            //发送文件--异步方法
            public void BeginSendFile(string filePath)
            {
                ParameterizedThreadStart start = new ParameterizedThreadStart(BeginSendFile);
                start.BeginInvoke(filePath,null,null);
            }
     
            private void BeginSendFile(object obj)
            {
                string filePath = obj as string;
                SendFile(filePath);
            }
            //发送文件
            public void SendFile(string filePath)
            {
                IPAddress ip = IPAddress.Parse("192.168.3.19");
                TcpListener listener = new TcpListener(ip,0);
                listener.Start();
     
                //获取本地监听的port号
                IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint;
                int listeningPort = endPoint.Port;
     
                //获取发送的协议字符串
                string fileName = Path.GetFileName(filePath);
                FileProtocol protocol = new FileProtocol(FileRequestMode.Send,listeningPort,fileName);
                string pro = protocol.ToString();
     
                SendMessage(pro);//发送协议到服务端
     
                //中断,等待远程连接
                TcpClient localClient = listener.AcceptTcpClient();
                Console.WriteLine("Start sending file...");
                NetworkStream stream = localClient.GetStream();
     
                //创建文件流
                FileStream fs = new FileStream(filePath,FileMode.Open,FileAccess.Read);
     
                byte[] fileBuffer = new byte[1024];
                //每次传输1KB
     
                int bytesRead;
                int totalBytes = 0;
     
                //创建获取文件发送状态的类
                SendStatus status = new SendStatus();
     
                //将文件流转写入网络流
                try
                {
                    do
                    {
                        Thread.Sleep(10);//模拟远程传输视觉效果,暂停10秒
                        bytesRead = fs.Read(fileBuffer, 0, fileBuffer.Length);
                        stream.Write(fileBuffer, 0, bytesRead);
                        totalBytes += bytesRead;
                        status.PrintStatus(totalBytes);
                    } while (bytesRead > 0);
                    Console.WriteLine("Total {0} bytes sent ,Done!", totalBytes);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                finally
                {
                    stream.Dispose();
                    fs.Dispose();
                    localClient.Close();
                    listener.Stop();
                }
            }
        }
    }
     


    SendStatus:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace Client
    {
        public class SendStatus
        {
            private FileInfo info;
            private long fileBytes;
     
            public SendStatus(string filePath)
            {
                info = new FileInfo(filePath);
                fileBytes = info.Length;        
            }
     
            public void PrintStatus(int sent)
            {
                string percent = GetPercent(sent);
                Console.WriteLine("Sending {0} bytes, {1}% ...",sent,percent);
            }
            //获得文件发送的百分比
            private string GetPercent(int sent)
            {
                decimal allBytes = Convert.ToDecimal(fileBytes);
                decimal currentSent = Convert.ToDecimal(sent);
                decimal percent = (currentSent / allBytes) * 100;
     
                percent = Math.Round(percent,1);//保留一位小数
     
                if (percent.ToString()=="100.0")
                {
                    return "100";
                }
                else
                {
                    return percent.ToString();
                }
            }
        }
    }
     


    主程序:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                ConsoleKey key;
                Client c = new Client();
                string filePath = Environment.CurrentDirectory + "/" + "Client01.jpg";
                if (File.Exists(filePath))
                {
                    c.BeginSendFile(filePath);
                }
                Console.WriteLine("
    
    按Q退出");
                do
                {
                    key = Console.ReadKey(true).Key;
                } while (key!=ConsoleKey.Q);            
            }
        }
    }
     


    这段代码游侠买几个地方须要注意:

    1.)Main()方法中能够看到,图片的位置为应用程序所在的文件夹.假设处于调试模式,那么在项目的Bin文件夹下的Debug文件夹中防止三种图片Client01.jpg,Client02.jpg,Client03.jpg,作为測试目的.

    2.)client提供了一个SendFile()和两个BeginSendFile()方法,分别用于同步和异步传输,当中私有的BeginSendFile()方法仅仅是个辅助方法,实际上对于发送文件这种操作,集合总是须要使用异步方式.

    3.)另外编写了一个SendStatus,用来记录和打印完毕的状态,已经发送了多少字节.

     

     

    2.服务端的实现

     

    服务端要完毕的就是解析协议,连接至client,依据协议内容,推断时发送文件还是接收文件,以下是代码部分:

     

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
     
    namespace Server
    {
        public class Server
        {
            private TcpClient client;
            private NetworkStream streamToClient;
            private const int BufferSize = 8192;
            private byte[] buffer;
            private ProtocolHandler handler;
     
     
            public Server(TcpClient client)
            {
                this.client = client;
     
                //打印连接到的客户端信息
                Console.WriteLine("ClientConnected! Local :{0}<--Client: {1}",client.Client.LocalEndPoint,client.Client.RemoteEndPoint);
     
                //获得流
                streamToClient = client.GetStream();
                buffer = new byte[BufferSize];
     
                handler = new ProtocolHandler();
                    
            }
     
            //開始进行读取
            public void BeginRead()
            {
                AsyncCallback callBack = new AsyncCallback(OnReadComplete);
                streamToClient.BeginRead(buffer,0,BufferSize,callBack,null);
            }
            //读取完毕时进行回调
            private void OnReadComplete(IAsyncResult ar)
            {
                int bytesRead = 0;
                try
                {
                    bytesRead = streamToClient.EndRead(ar);
                    Console.WriteLine("Reading data,{0} bytes...",bytesRead);
                    if (bytesRead==0)
                    {
                        Console.WriteLine("Client offline.");
                        return;
                    }
                    string msg = Encoding.Unicode.GetString(buffer,0,bytesRead);
                    Array.Clear(buffer,0,buffer.Length);//清空缓存,避免脏读
     
                    //获取protocol数组
                    string[] protocolArray = handler.GetProtocol(msg);
                    foreach (string pro in protocolArray)
                    {
                        //这里异步调用,不然这里会比較耗时
                        ParameterizedThreadStart start = new ParameterizedThreadStart(handleProtocol);
                        start.BeginInvoke(pro,null,null);
                    }
                    //再次调用BeginRead(),完毕时调用自身,形成无限循环
                    AsyncCallback callBack = new AsyncCallback(OnReadComplete);
                    streamToClient.BeginRead(buffer,0,BufferSize,callBack,null);
                }
                catch (Exception ex)
                {
                    if (streamToClient!=null)
                    {
                        streamToClient.Dispose();
                    }
                    client.Close();
                    Console.WriteLine(ex.Message);                
                }
            }
            //处理protocol
            private void handleProtocol(object obj)
            {
                string pro = obj as string;
                ProtocolHelper helper = new ProtocolHelper(pro);
                FileProtocol protocol = helper.GetProtocol();
     
                if (protocol.Mode==FileRequestMode.Send)
                {
                    //客户端发送文件,对服务端来说则是接收文件
                    receiveFile(protocol);
                }
                else if (protocol.Mode==FileRequestMode.Receive)
                {
                    //客户端接收文件,对服务端来说是发送文件
                    //sendFile(protocol);
                }
            }
            //接收文件
            private void receiveFile(FileProtocol protocol)
            {
                //获取远程客户端的位置
                IPEndPoint endPoint = client.Client.RemoteEndPoint as IPEndPoint;
                IPAddress ip = endPoint.Address;
     
                //使用新port,获得远程用于接收文件的port
                endPoint = new IPEndPoint(ip,protocol.Port);
     
                //连接到远程客户端
                TcpClient localClient;
                try
                {
                    localClient = new TcpClient();
                    localClient.Connect(endPoint);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("无法连接到客户端 --> {0}",endPoint);
                    return;
                }
     
                //获取发送文件的流
                NetworkStream streamToCLient = localClient.GetStream();
     
                //随机生成一个在当前文件夹下的文件名
                string path = Environment.CurrentDirectory + "/" + generateFileName(protocol.FileName);
     
                byte[] fileBuffer = new byte[1024];//每次收1KB
                FileStream fs = new FileStream(path,FileMode.CreateNew,FileAccess.Write);
     
                //从缓存Buffer中读入到文件流中
                int bytesRead;
                int totalBytes = 0;
                do
                {
                    bytesRead = streamToCLient.Read(buffer,0,BufferSize);
     
                    fs.Write(buffer, 0, bytesRead);
                    totalBytes += bytesRead;
                    Console.WriteLine("Receiving {0} bytes ...",totalBytes);
                } while (bytesRead>0);
                Console.WriteLine("Total {0} bytes received,Done! ",totalBytes);
     
                streamToClient.Dispose();
                fs.Dispose();
                localClient.Close();
            }
            //随机获取一个图片名称
            private string generateFileName(string fileName)
            {
                DateTime now = DateTime.Now;
                return string.Format("{0}_{1}_{2}_{3}",now.Minute,now.Second,now.Millisecond,fileName);
            }
        }
    }
     

     

    主程序例如以下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading.Tasks;
     
    namespace Server
    {    
        class Program
        {
            static void Main(string[] args)
            {
     
                Console.WriteLine("Server is running...");
                IPAddress ip = IPAddress.Parse("192.168.1.113");
                TcpListener listener = new TcpListener(ip,8500);
                listener.Start();
                Console.WriteLine("Start Listening...");
                while (true)
                {
                    //获取一个连接,同步方法,在此处中断
                    TcpClient client = listener.AcceptTcpClient();
                    Server wapper = new Server(client);
                    wapper.BeginRead();
                }
            }
        }
    }
     


    这段代码须要注意这么几点:

    1.在OnReadComplete()回调方法的foreach循环中,使用托付异步调用了handlerProtocol()方法,这是由于handlerProtocol即即将运行的是一个读取或接收文件的操作,也就是一个相对耗时的操作.

    2.在handlerProtocol()方法中,能够体会到定义ProtocolHelper类和FileProtocol结构的优点,假设不定义它们,这里将是杂乱的处理XML以及类型转换的代码.

    3.handlerProtocol()方法中进行了一个条件推断,注意sendFile()被屏蔽了,这里还没有为服务单发送文件至client.

    4.receiveFile()方法是实际接受client发来文件的方法,这里没有什么特别之处.须要注意的是文件存储的路径,它保存在了当前程序运行的文件夹下,文件的名称使用GetFileName()方法生成一个与时间有关的随机名称.

     

    最后我想说一点,由于楼主中途换个一个教室,程序怎么都出错,结果搞了半天发现是IP地址出了问题,每一个教室的IP地址可能不同,另一点须要注意,别忘了加入对解决方式的引用,要不然会报错.

     

    无论怎么说,程序算是完毕了,楼主这里用了一个40M大小的文件进行測试(原本是一个avi,楼主改了一下后缀名)....程序是如此的慢,为啥呢?由于咱们一次仅仅传输1KB...

     

    到如今为止,咱们关于网络编程的内容算是搞一个段落了,开头第一部分解说了TCP协议,套接字,即时通信程序三种开发模式,以及两个基本操作----监听port和连接远程server.

     

    第二部分是一个简单的实例:从client传输字符串到server,server接受并处理字符串,在将处理过的字符串发回给client.

     

    第三部分是异步传输和文本边界,这是对第二部分的强化.

     

    第四部分是发送文件.

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法训练 一元三次方程
    Java实现 蓝桥杯VIP 算法训练 乘法表
    Java实现 蓝桥杯VIP 算法训练 矩阵加法
    Java实现 蓝桥杯VIP 算法训练 一元三次方程
    Java实现 蓝桥杯VIP 算法训练 平方计算
    Java实现 蓝桥杯VIP 算法训练 平方计算
    Java实现 蓝桥杯VIP 算法训练 平方计算
    Java实现 蓝桥杯VIP 算法训练 乘法表
    Java实现 蓝桥杯VIP 算法训练 乘法表
    监管只是压倒网盘业务的一根稻草,但不是主要原因(答案只有一个:成本!)
  • 原文地址:https://www.cnblogs.com/yfceshi/p/7283527.html
Copyright © 2011-2022 走看看