日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

文件断点续传原理与实现

發(fā)布時(shí)間:2023/12/20 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 文件断点续传原理与实现 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文件斷點(diǎn)續(xù)傳原理與實(shí)現(xiàn)

在網(wǎng)絡(luò)狀況不好的情況下,對(duì)于文件的傳輸,我們希望能夠支持可以每次傳部分?jǐn)?shù)據(jù)。首先從文件傳輸協(xié)議FTP和TFTP開始分析,

FTP是基于TCP的,一般情況下建立兩個(gè)連接,一個(gè)負(fù)責(zé)指令,一個(gè)負(fù)責(zé)數(shù)據(jù);而TFTP是基于UDP的,由于UDP傳輸是不可靠的,雖然傳輸速度很快,但對(duì)于普通的文件像PDF這種,少了一個(gè)字節(jié)都不行。本次以IM中的文件下載場(chǎng)景為例,解析基于TCP的文件斷點(diǎn)續(xù)傳的原理,并用代碼實(shí)現(xiàn)。

什么是斷點(diǎn)續(xù)傳?

斷點(diǎn)續(xù)傳其實(shí)正如字面意思,就是在下載的斷開點(diǎn)繼續(xù)開始傳輸,不用再?gòu)念^開始。所以理解斷點(diǎn)續(xù)傳的核心后,發(fā)現(xiàn)其實(shí)和很簡(jiǎn)單,關(guān)鍵就在于對(duì)傳輸中斷點(diǎn)的把握,我就自己的理解畫了一個(gè)簡(jiǎn)單的示意圖:

原理:

斷點(diǎn)續(xù)傳的關(guān)鍵是斷點(diǎn),所以在制定傳輸協(xié)議的時(shí)候要設(shè)計(jì)好,如上圖,我自定義了一個(gè)交互協(xié)議,每次下載請(qǐng)求都會(huì)帶上下載的起始點(diǎn),這樣就可以支持從斷點(diǎn)下載了,其實(shí)HTTP里的斷點(diǎn)續(xù)傳也是這個(gè)原理,在HTTP的頭里有個(gè)可選的字段RANGE,表示下載的范圍,下面是我用Java語(yǔ)言實(shí)現(xiàn)的下載斷點(diǎn)續(xù)傳示例。

提供下載的服務(wù)端代碼:

[java]?view plaincopy
  • import?java.io.File;??
  • import?java.io.IOException;??
  • import?java.io.InputStream;??
  • import?java.io.OutputStream;??
  • import?java.io.RandomAccessFile;??
  • import?java.io.StringWriter;??
  • import?java.net.ServerSocket;??
  • import?java.net.Socket;??
  • ??
  • //?斷點(diǎn)續(xù)傳服務(wù)端??
  • public?class?FTPServer?{??
  • ??
  • ????//?文件發(fā)送線程??
  • ????class?Sender?extends?Thread{??
  • ????????//?網(wǎng)絡(luò)輸入流??
  • ????????private?InputStream?in;??
  • ????????//?網(wǎng)絡(luò)輸出流??
  • ????????private?OutputStream?out;??
  • ????????//?下載文件名??
  • ????????private?String?filename;??
  • ??
  • ????????public?Sender(String?filename,?Socket?socket){??
  • ????????????try?{??
  • ????????????????this.out?=?socket.getOutputStream();??
  • ????????????????this.in?=?socket.getInputStream();??
  • ????????????????this.filename?=?filename;??
  • ????????????}?catch?(IOException?e)?{??
  • ????????????????e.printStackTrace();??
  • ????????????}??
  • ????????}??
  • ??????????
  • ????????@Override??
  • ????????public?void?run()?{??
  • ????????????try?{??
  • ????????????????System.out.println("start?to?download?file!");??
  • ????????????????int?temp?=?0;??
  • ????????????????StringWriter?sw?=?new?StringWriter();??
  • ????????????????while((temp?=?in.read())?!=?0){??
  • ????????????????????sw.write(temp);??
  • ????????????????????//sw.flush();??
  • ????????????????}??
  • ????????????????//?獲取命令??
  • ????????????????String?cmds?=?sw.toString();??
  • ????????????????System.out.println("cmd?:?"?+?cmds);??
  • ????????????????if("get".equals(cmds)){??
  • ????????????????????//?初始化文件??
  • ????????????????????File?file?=?new?File(this.filename);??
  • ????????????????????RandomAccessFile?access?=?new?RandomAccessFile(file,"r");??
  • ????????????????????//??
  • ????????????????????StringWriter?sw1?=?new?StringWriter();??
  • ????????????????????while((temp?=?in.read())?!=?0){??
  • ????????????????????????sw1.write(temp);??
  • ????????????????????????sw1.flush();??
  • ????????????????????}??
  • ????????????????????System.out.println(sw1.toString());??
  • ????????????????????//?獲取斷點(diǎn)位置??
  • ????????????????????int?startIndex?=?0;??
  • ????????????????????if(!sw1.toString().isEmpty()){??
  • ????????????????????????startIndex?=?Integer.parseInt(sw1.toString());??
  • ????????????????????}??
  • ????????????????????long?length?=?file.length();??
  • ????????????????????byte[]?filelength?=?String.valueOf(length).getBytes();??
  • ????????????????????out.write(filelength);??
  • ????????????????????out.write(0);??
  • ????????????????????out.flush();??
  • ????????????????????//?計(jì)劃要讀的文件長(zhǎng)度??
  • ????????????????????//int?length?=?(int)?file.length();//Integer.parseInt(sw2.toString());??
  • ????????????????????System.out.println("file?length?:?"?+?length);??
  • ????????????????????//?緩沖區(qū)10KB??
  • ????????????????????byte[]?buffer?=?new?byte[1024*10];??
  • ????????????????????//?剩余要讀取的長(zhǎng)度??
  • ????????????????????int?tatol?=?(int)?length;??
  • ????????????????????System.out.println("startIndex?:?"?+?startIndex);??
  • ????????????????????access.skipBytes(startIndex);??
  • ????????????????????while?(true)?{??
  • ????????????????????????//?如果剩余長(zhǎng)度為0則結(jié)束??
  • ????????????????????????if(tatol?==?0){??
  • ????????????????????????????break;??
  • ????????????????????????}??
  • ????????????????????????//?本次要讀取的長(zhǎng)度假設(shè)為剩余長(zhǎng)度??
  • ????????????????????????int?len?=?tatol?-?startIndex;??
  • ????????????????????????//?如果本次要讀取的長(zhǎng)度大于緩沖區(qū)的容量??
  • ????????????????????????if(len?>?buffer.length){??
  • ????????????????????????????//?修改本次要讀取的長(zhǎng)度為緩沖區(qū)的容量??
  • ????????????????????????????len?=?buffer.length;??
  • ????????????????????????}??
  • ????????????????????????//?讀取文件,返回真正讀取的長(zhǎng)度??
  • ????????????????????????int?rlength?=?access.read(buffer,0,len);??
  • ????????????????????????//?將剩余要讀取的長(zhǎng)度減去本次已經(jīng)讀取的??
  • ????????????????????????tatol?-=?rlength;??
  • ????????????????????????//?如果本次讀取個(gè)數(shù)不為0則寫入輸出流,否則結(jié)束??
  • ????????????????????????if(rlength?>?0){??
  • ????????????????????????????//?將本次讀取的寫入輸出流中??
  • ????????????????????????????out.write(buffer,0,rlength);??
  • ????????????????????????????out.flush();??
  • ????????????????????????}?else?{??
  • ????????????????????????????break;??
  • ????????????????????????}??
  • ????????????????????????//?輸出讀取進(jìn)度??
  • ????????????????????????//System.out.println("finish?:?"?+?((float)(length?-tatol)?/?length)?*100?+?"?%");??
  • ????????????????????}??
  • ????????????????????//System.out.println("receive?file?finished!");??
  • ????????????????????//?關(guān)閉流??
  • ????????????????????out.close();??
  • ????????????????????in.close();??
  • ????????????????????access.close();??
  • ????????????????}??
  • ????????????}?catch?(IOException?e)?{??
  • ????????????????e.printStackTrace();??
  • ????????????}??
  • ????????????super.run();??
  • ????????}??
  • ????}??
  • ??????
  • ????public?void?run(String?filename,?Socket?socket){??
  • ????????//?啟動(dòng)接收文件線程???
  • ????????new?Sender(filename,socket).start();??
  • ????}??
  • ??????
  • ????public?static?void?main(String[]?args)?throws?Exception?{??
  • ????????//?創(chuàng)建服務(wù)器監(jiān)聽??
  • ????????ServerSocket?server?=?new?ServerSocket(8888);??
  • ????????//?接收文件的保存路徑??
  • ????????String?filename?=?"E:\\ceshi\\mm.pdf";??
  • ????????for(;;){??
  • ????????????Socket?socket?=?server.accept();??
  • ????????????new?FTPServer().run(filename,?socket);??
  • ????????}??
  • ????}??
  • ??
  • }??

  • 下載的客戶端代碼:

    [java]?view plaincopy
  • import?java.io.File;??
  • import?java.io.InputStream;??
  • import?java.io.OutputStream;??
  • import?java.io.RandomAccessFile;??
  • import?java.io.StringWriter;??
  • import?java.net.InetSocketAddress;??
  • import?java.net.Socket;??
  • ??
  • //?斷點(diǎn)續(xù)傳客戶端??
  • public?class?FTPClient?{??
  • ??
  • ????/**?
  • ?????*??request:get0startIndex0?
  • ?????*??response:fileLength0fileBinaryStream?
  • ?????*???
  • ?????*?@param?filepath?
  • ?????*?@throws?Exception?
  • ?????*/??
  • ????public?void?Get(String?filepath)?throws?Exception?{??
  • ????????Socket?socket?=?new?Socket();??
  • ????????//?建立連接??
  • ????????socket.connect(new?InetSocketAddress("127.0.0.1",?8888));??
  • ????????//?獲取網(wǎng)絡(luò)流??
  • ????????OutputStream?out?=?socket.getOutputStream();??
  • ????????InputStream?in?=?socket.getInputStream();??
  • ????????//?文件傳輸協(xié)定命令??
  • ????????byte[]?cmd?=?"get".getBytes();??
  • ????????out.write(cmd);??
  • ????????out.write(0);//?分隔符??
  • ????????int?startIndex?=?0;??
  • ????????//?要發(fā)送的文件??
  • ????????File?file?=?new?File(filepath);??
  • ????????if(file.exists()){??
  • ????????????startIndex?=?(int)?file.length();??
  • ????????}??
  • ????????System.out.println("Client?startIndex?:?"?+?startIndex);??
  • ????????//?文件寫出流??
  • ????????RandomAccessFile?access?=?new?RandomAccessFile(file,"rw");??
  • ????????//?斷點(diǎn)??
  • ????????out.write(String.valueOf(startIndex).getBytes());??
  • ????????out.write(0);??
  • ????????out.flush();??
  • ????????//?文件長(zhǎng)度??
  • ????????int?temp?=?0;??
  • ????????StringWriter?sw?=?new?StringWriter();??
  • ????????while((temp?=?in.read())?!=?0){??
  • ????????????sw.write(temp);??
  • ????????????sw.flush();??
  • ????????}??
  • ????????int?length?=?Integer.parseInt(sw.toString());??
  • ????????System.out.println("Client?fileLength?:?"?+?length);??
  • ????????//?二進(jìn)制文件緩沖區(qū)??
  • ????????byte[]?buffer?=?new?byte[1024*10];??
  • ????????//?剩余要讀取的長(zhǎng)度??
  • ????????int?tatol?=?length?-?startIndex;??
  • ????????//??
  • ????????access.skipBytes(startIndex);??
  • ????????while?(true)?{??
  • ????????????//?如果剩余長(zhǎng)度為0則結(jié)束??
  • ????????????if?(tatol?==?0)?{??
  • ????????????????break;??
  • ????????????}??
  • ????????????//?本次要讀取的長(zhǎng)度假設(shè)為剩余長(zhǎng)度??
  • ????????????int?len?=?tatol;??
  • ????????????//?如果本次要讀取的長(zhǎng)度大于緩沖區(qū)的容量??
  • ????????????if?(len?>?buffer.length)?{??
  • ????????????????//?修改本次要讀取的長(zhǎng)度為緩沖區(qū)的容量??
  • ????????????????len?=?buffer.length;??
  • ????????????}??
  • ????????????//?讀取文件,返回真正讀取的長(zhǎng)度??
  • ????????????int?rlength?=?in.read(buffer,?0,?len);??
  • ????????????//?將剩余要讀取的長(zhǎng)度減去本次已經(jīng)讀取的??
  • ????????????tatol?-=?rlength;??
  • ????????????//?如果本次讀取個(gè)數(shù)不為0則寫入輸出流,否則結(jié)束??
  • ????????????if?(rlength?>?0)?{??
  • ????????????????//?將本次讀取的寫入輸出流中??
  • ????????????????access.write(buffer,?0,?rlength);??
  • ????????????}?else?{??
  • ????????????????break;??
  • ????????????}??
  • ????????????System.out.println("finish?:?"?+?((float)(length?-tatol)?/?length)?*100?+?"?%");??
  • ????????}??
  • ????????System.out.println("finished!");??
  • ????????//?關(guān)閉流??
  • ????????access.close();??
  • ????????out.close();??
  • ????????in.close();??
  • ????}??
  • ??
  • ????public?static?void?main(String[]?args)?{??
  • ????????FTPClient?client?=?new?FTPClient();??
  • ????????try?{??
  • ????????????client.Get("E:\\ceshi\\test\\mm.pdf");??
  • ????????}?catch?(Exception?e)?{??
  • ????????????e.printStackTrace();??
  • ????????}??
  • ????}??
  • }??
  • 測(cè)試
    原文件、下載中途斷開的文件和從斷點(diǎn)下載后的文件分別從左至右如下:


    斷點(diǎn)前的傳輸進(jìn)度如下(中途省略):

    Client fileLength : 51086228
    finish : 0.020044541 %
    finish : 0.040089082 %
    finish : 0.060133625 %
    finish : 0.07430574 %
    finish : 0.080178164 %
    ...
    finish : 60.41171 %
    finish : 60.421593 %
    finish : 60.428936 %
    finish : 60.448982 %
    finish : 60.454338 %

    斷開的點(diǎn)計(jì)算:30883840 / 51086228 = 0.604543361471119 * 100% = 60.45433614%

    從斷點(diǎn)后開始傳的進(jìn)度(中途省略):
    Client startIndex : 30883840
    Client fileLength : 51086228
    finish : 60.474377 %
    finish : 60.494423 %
    finish : 60.51447 %
    finish : 60.53451 %
    finish : 60.554558 %
    ...
    finish : 99.922035 %
    finish : 99.942085 %
    finish : 99.95677 %
    finish : 99.96213 %
    finish : 99.98217 %
    finish : 100.0 %
    finished!

    斷點(diǎn)處前后的百分比計(jì)算如下:

    ============================下面是從斷點(diǎn)開始的進(jìn)度==============================

    本方案是基于TCP,在本方案設(shè)計(jì)之初,我還探索了一下介于TCP與UDP之間的一個(gè)協(xié)議:UDT(基于UDP的可靠傳輸協(xié)議)。

    我基于Netty寫了相關(guān)的測(cè)試代碼,用Wireshark拆包發(fā)現(xiàn)的確是UDP的包,而且是要建立連接的,與UDP不同的是需要建立連接,所說UDT的傳輸性能比TCP好,傳輸?shù)目煽啃员萓DP好,屬于兩者的一個(gè)平衡的選擇,感興的可以深入研究一下。

    總結(jié)

    以上是生活随笔為你收集整理的文件断点续传原理与实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。