生活随笔
收集整理的這篇文章主要介紹了
文件断点续传原理与实现
小編覺得挺不錯(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;?? ?? ?? public?class?FTPServer?{?? ?? ?????? ????class?Sender?extends?Thread{?? ?????????? ????????private?InputStream?in;?? ?????????? ????????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);?? ?????????????????????? ????????????????}?? ?????????????????? ????????????????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());?? ?????????????????????? ????????????????????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();?? ?????????????????????? ?????????????????????? ????????????????????System.out.println("file?length?:?"?+?length);?? ?????????????????????? ????????????????????byte[]?buffer?=?new?byte[1024*10];?? ?????????????????????? ????????????????????int?tatol?=?(int)?length;?? ????????????????????System.out.println("startIndex?:?"?+?startIndex);?? ????????????????????access.skipBytes(startIndex);?? ????????????????????while?(true)?{?? ?????????????????????????? ????????????????????????if(tatol?==?0){?? ????????????????????????????break;?? ????????????????????????}?? ?????????????????????????? ????????????????????????int?len?=?tatol?-?startIndex;?? ?????????????????????????? ????????????????????????if(len?>?buffer.length){?? ?????????????????????????????? ????????????????????????????len?=?buffer.length;?? ????????????????????????}?? ?????????????????????????? ????????????????????????int?rlength?=?access.read(buffer,0,len);?? ?????????????????????????? ????????????????????????tatol?-=?rlength;?? ?????????????????????????? ????????????????????????if(rlength?>?0){?? ?????????????????????????????? ????????????????????????????out.write(buffer,0,rlength);?? ????????????????????????????out.flush();?? ????????????????????????}?else?{?? ????????????????????????????break;?? ????????????????????????}?? ?????????????????????????? ?????????????????????????? ????????????????????}?? ?????????????????????? ?????????????????????? ????????????????????out.close();?? ????????????????????in.close();?? ????????????????????access.close();?? ????????????????}?? ????????????}?catch?(IOException?e)?{?? ????????????????e.printStackTrace();?? ????????????}?? ????????????super.run();?? ????????}?? ????}?? ?????? ????public?void?run(String?filename,?Socket?socket){?? ?????????? ????????new?Sender(filename,socket).start();?? ????}?? ?????? ????public?static?void?main(String[]?args)?throws?Exception?{?? ?????????? ????????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;?? ?? ?? public?class?FTPClient?{?? ?? ????? ? ? ? ? ? ?? ????public?void?Get(String?filepath)?throws?Exception?{?? ????????Socket?socket?=?new?Socket();?? ?????????? ????????socket.connect(new?InetSocketAddress("127.0.0.1",?8888));?? ?????????? ????????OutputStream?out?=?socket.getOutputStream();?? ????????InputStream?in?=?socket.getInputStream();?? ?????????? ????????byte[]?cmd?=?"get".getBytes();?? ????????out.write(cmd);?? ????????out.write(0);?? ????????int?startIndex?=?0;?? ?????????? ????????File?file?=?new?File(filepath);?? ????????if(file.exists()){?? ????????????startIndex?=?(int)?file.length();?? ????????}?? ????????System.out.println("Client?startIndex?:?"?+?startIndex);?? ?????????? ????????RandomAccessFile?access?=?new?RandomAccessFile(file,"rw");?? ?????????? ????????out.write(String.valueOf(startIndex).getBytes());?? ????????out.write(0);?? ????????out.flush();?? ?????????? ????????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);?? ?????????? ????????byte[]?buffer?=?new?byte[1024*10];?? ?????????? ????????int?tatol?=?length?-?startIndex;?? ?????????? ????????access.skipBytes(startIndex);?? ????????while?(true)?{?? ?????????????? ????????????if?(tatol?==?0)?{?? ????????????????break;?? ????????????}?? ?????????????? ????????????int?len?=?tatol;?? ?????????????? ????????????if?(len?>?buffer.length)?{?? ?????????????????? ????????????????len?=?buffer.length;?? ????????????}?? ?????????????? ????????????int?rlength?=?in.read(buffer,?0,?len);?? ?????????????? ????????????tatol?-=?rlength;?? ?????????????? ????????????if?(rlength?>?0)?{?? ?????????????????? ????????????????access.write(buffer,?0,?rlength);?? ????????????}?else?{?? ????????????????break;?? ????????????}?? ????????????System.out.println("finish?:?"?+?((float)(length?-tatol)?/?length)?*100?+?"?%");?? ????????}?? ????????System.out.println("finished!");?? ?????????? ????????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ò),歡迎將生活随笔推薦給好友。