文件的断点续传

转自: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'来补充,这些内容也会发送给接收端,从而导致一些莫名的问题。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值