文件的断点续传

来源:互联网 发布:淘宝购物助手 编辑:程序博客网 时间:2024/06/08 06:52

转自:http://apps.hi.baidu.com/share/detail/31497239

所谓的断点续传就是指:文件在传输过程式中被中断后,在重新传输时,可以从上次的断点处开始传输,这样就可节省时间,和其它资源。

实现关键在这里有两个关键点

其一是检测本地已经下载的文件长度和断点值;

其二是在服务端调整文件指针到断点处

实现方法:

我们用一个简单的方法来实现断点续传的功能:在传输文件的时候创建一个临时文件用来存放文件的断点位置

在每次发送文件时,先检查有没有临时文件;如果有的话,就从临时文件中读取断点值,并把文件指针移动到断点位置开始传输,这样便可以做到断点续传了。

实现流程:

首次传输其流程如下:

1. 服务端向客户端传递文件名称和文件长度;

2. 根据文件长度计算文件块数(文件分块传输请参照第二篇文章);

3. 客户端将传输的块数写入临时文件(做为断点值);

4. 若文件传输成功则删除临时文件;

首次传输失败后将按以下流程进行:

1. 客户端从临时文件读取断点值并发送给服务端

2. 服务端与客户端将文件指针移至断点处;

3. 从断点处传输文件;

接收端和发送端的通信过程如下:


编码实现:

接收端代码如下:

        //监听线程回调函数        private void lstnThreadProc()        {            Console.WriteLine("监听线程开始执行");            using (Socket lstnSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))            {                lstnSock.Bind(m_lstnAddr);                lstnSock.Listen(m_lstnNum);                while(true)                {                    Socket commSock = lstnSock.Accept();                    Console.WriteLine("接收到连接请求,并建立连接");                    Thread recvThread=new Thread(new ParameterizedThreadStart(recvThreadProc));                    recvThread.SetApartmentState(ApartmentState.STA);                    recvThread.Start(commSock);                }            }
每监听到一个连接,则创建一个新的线程来负责与发送端的通信和文件的传输

        //接收线程        private void recvThreadProc(object argument)        {            //for check            Console.WriteLine("接收线程开始执行");            try            {                Socket commSock=(Socket)argument;                byte[] recvBuf = new byte[128];                int nRecv = commSock.Receive(recvBuf);                if (nRecv <= 0)                {                    Console.WriteLine("接收到字节{0}", nRecv);                    return;                }                //第一次接收到的信息为文件信息                TransFileInfo recvFileInfo = (TransFileInfo)StructTranslate.BytesToStruct(recvBuf, typeof(TransFileInfo));                //获取文件信息                string fileName = new string(recvFileInfo.fileName).TrimEnd('\0');                string fileExtension = new string(recvFileInfo.fileExtension).TrimEnd('\0');                long fileSize = int.Parse(new string(recvFileInfo.fileSize).TrimEnd('\0'));                Console.WriteLine("是否接收文件,信息如下:\n文件名:{0}\t文件长度:{1}\n", fileName, fileSize);                DialogResult result = MessageBox.Show("是否接收文件"+fileName, "白氏文件传输软件", MessageBoxButtons.YesNo);                                //只有用户选择接收文件,才进行,否则直接断开连接                if (result == DialogResult.Yes)                {                    //读取传输断点值                    int nBlockNum = readTransPos(fileName);                    byte[] sendBuf = BitConverter.GetBytes(nBlockNum);                    //发送给对方断点值                    commSock.Send(sendBuf);                                        //接收文件                    //弹出选择保存路径的对话框                    SaveFileDialog saveFileDlg = new SaveFileDialog();                    saveFileDlg.FileName = fileName;                    saveFileDlg.Filter = string.Format("默认类型|*{0}|所有文件(*.*)|*.*", fileExtension);                    if (saveFileDlg.ShowDialog() != DialogResult.OK)                    {                        //关闭套接字,并返回                        commSock.Close();                        return;                    }                    //保存路径                    string fileFullName = saveFileDlg.FileName;                    recvFile(commSock, fileFullName, fileSize, nBlockNum);                    //断开连接                    commSock.Close();                }            }            catch(Exception e)            {                Console.WriteLine(e.Message);            }        }
接收线程负责与发送端通信:发送端发送来文件信息,在进行解析后,在XML文件中读取是否有其临时文件,以读取已传输的块数。通信过程结束后,开始接收文件

        //具体的接收过程        private void recvFile(Socket recvSock, string fileFullName, long fileSize, int nBlockNum)        {            //打开文件,并指到传输位置            using (FileStream writeFile = new FileStream(fileFullName, FileMode.OpenOrCreate, FileAccess.Write))            {                int nOffset = nBlockNum * m_nBlockSize;                writeFile.Seek(nOffset, 0);                //接收传输过来的文件                byte[] recvBuf = new byte[m_nBlockSize];                int nRecv = 0;                while (true)                {                    try                    {                        nRecv = recvSock.Receive(recvBuf);                        writeFile.Write(recvBuf, 0, nRecv);                        //如果接收到的数据不够缓存大小,表示传输结束                        if (nRecv <= 0)                        {                            RecvProgressEvent(100);                            writeTransPos(fileFullName, fileSize, nBlockNum);                            writeFile.Close();                            Console.WriteLine("文件{0}传输完成", fileFullName);                            return;                        }                        nBlockNum++;                        RecvProgressEvent((int)(nBlockNum*m_nBlockSize/fileSize)*100);                    }                    catch (Exception e)                    {                        //写到外存中                        writeTransPos(fileFullName, nBlockNum);                        writeFile.Close();                        Console.WriteLine("传输未完成,错误信息为:" + e.Message);                        return;                    }                }            }        }

发送端代码如下:

        //创建一个线程,用于发送文件        public void sendThreadProc(object argument)        {            string fileFullName = (string)argument;            //获取文件信息            FileInfo sendFileInfo = new FileInfo(fileFullName);            //for check            Console.WriteLine("所选择文件信息如下:\n文件名:{0}\t文件扩展名:{1}\t文件大小(字节数):{2}", sendFileInfo.Name, sendFileInfo.Extension, sendFileInfo.Length);            using(Socket commSock=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))            {                //与对方建立连接                commSock.Connect(m_remoteAddr);                Console.WriteLine("成功与接收方连接");                //发送文件信息(文件名,扩展名,文件大小)                TransFileInfo fileInfo = new TransFileInfo(sendFileInfo.Name, sendFileInfo.Extension, sendFileInfo.Length);                byte[] sendBuf = StructTranslate.StructToBytes((object)fileInfo);                commSock.Send(sendBuf);                //接收已传输的文件块个数                byte[] recvBuf = new byte[128];                commSock.Receive(recvBuf);                int nBlockNum = BitConverter.ToInt32(recvBuf, 0);                //只负责文件的发送                SendFile(commSock, fileFullName, sendFileInfo.Length, nBlockNum);                commSock.Close();            }        }
每发送一个文件,就创建一个线程,用于与接收端通信(发送文件信息,接收已传输的块数),以及发送文件

        //具体的传送函数(参数分别为:文件名(包含绝对路径名),已传块数)        public void SendFile(Socket sendSock, string fileFullName, long fileSize, int nBlockNum)        {             //打开文件,并指到已传输的位置的下一处            using (FileStream readFile = new FileStream(fileFullName, FileMode.Open, FileAccess.Read))            {                int nOffset = m_nBlockSize * nBlockNum;                readFile.Seek(nOffset, 0);                byte[] sendBuf = new byte[m_nBlockSize];                while (true)                {                    //从文件中读取到发送缓存中                    int nRead = readFile.Read(sendBuf, 0, m_nBlockSize);                    //发送数据                    int nSend = sendSock.Send(sendBuf, nRead, SocketFlags.None);                    if (nRead <= 0)                    {                        SendProgressEvent(100);                        Console.WriteLine("传输完毕");                        readFile.Close();                        break;                    }                    SendProgressEvent((int)((nOffset+nSend)*100/fileSize));                }            }        }
注意:

(1) 在while(true)循环中,不能用nSend<m_nBlockSize来判断是否终结。因为sendBuf[ ]是m_nBlockSize大小的字节数组,所以发送的时候发送了m_nBlockSize,即nSend=m_nBlockSize

(2) sendSock.send()方法中要注意,如果直接使用Send(sendBuf); 的话,会使sendBuf[]中不足m_nBlockSize的部分用'\0'来补充,这些内容也会发送给接收端,从而导致一些莫名的问题。

原创粉丝点击