生活随笔
收集整理的這篇文章主要介紹了
文件断点续传原理与实现
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
文件斷點續傳原理與實現
在網絡狀況不好的情況下,對于文件的傳輸,我們希望能夠支持可以每次傳部分數據。首先從文件傳輸協議FTP和TFTP開始分析,
FTP是基于TCP的,一般情況下建立兩個連接,一個負責指令,一個負責數據;而TFTP是基于UDP的,由于UDP傳輸是不可靠的,雖然傳輸速度很快,但對于普通的文件像PDF這種,少了一個字節都不行。本次以IM中的文件下載場景為例,解析基于TCP的文件斷點續傳的原理,并用代碼實現。
什么是斷點續傳?
斷點續傳其實正如字面意思,就是在下載的斷開點繼續開始傳輸,不用再從頭開始。所以理解斷點續傳的核心后,發現其實和很簡單,關鍵就在于對傳輸中斷點的把握,我就自己的理解畫了一個簡單的示意圖:
原理:
斷點續傳的關鍵是斷點,所以在制定傳輸協議的時候要設計好,如上圖,我自定義了一個交互協議,每次下載請求都會帶上下載的起始點,這樣就可以支持從斷點下載了,其實HTTP里的斷點續傳也是這個原理,在HTTP的頭里有個可選的字段RANGE,表示下載的范圍,下面是我用Java語言實現的下載斷點續傳示例。
提供下載的服務端代碼:
[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();?? ????????}?? ????}?? }?? 測試 原文件、下載中途斷開的文件和從斷點下載后的文件分別從左至右如下:
斷點前的傳輸進度如下(中途省略):
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 %
斷開的點計算:30883840 / 51086228 = 0.604543361471119 * 100% = 60.45433614%
從斷點后開始傳的進度(中途省略):
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!
斷點處前后的百分比計算如下:
============================下面是從斷點開始的進度==============================
本方案是基于TCP,在本方案設計之初,我還探索了一下介于TCP與UDP之間的一個協議:UDT(基于UDP的可靠傳輸協議)。
我基于Netty寫了相關的測試代碼,用Wireshark拆包發現的確是UDP的包,而且是要建立連接的,與UDP不同的是需要建立連接,所說UDT的傳輸性能比TCP好,傳輸的可靠性比UDP好,屬于兩者的一個平衡的選擇,感興的可以深入研究一下。
總結
以上是生活随笔為你收集整理的文件断点续传原理与实现的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。