(1) 再開可能なダウンロードの原理<BR>実際、再開可能なダウンロードの原理は非常に単純で、HTTP リクエストが一般的なダウンロードと異なるだけです。
たとえば、ブラウザがサーバー上のファイルをリクエストする場合、ブラウザは次のようなリクエストを行います。
サーバーのドメイン名が wwww.sjtu.edu.cn で、ファイル名が down.zip であるとします。
/down.zip HTTP/1.1 を取得する
受け入れる: image/gif、image/x-xbitmap、image/jpeg、image/pjpeg、application/vnd.ms-
Excel、アプリケーション/msword、アプリケーション/vnd.ms-powerpoint、*/*
受け入れ言語: zh-cn
Accept-Encoding: gzip、deflate
ユーザーエージェント: Mozilla/4.0 (互換性、MSIE 5.01、Windows NT 5.0)
接続: キープアライブ
リクエストを受信したサーバーは、必要に応じてリクエストされたファイルを検索し、ファイル情報を抽出してブラウザに返します。返される情報は次のとおりです。
200
コンテンツの長さ=106786028
Accept-Range=バイト
日付=2001年4月30日月曜日12:56:11 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=アプリケーション/オクテットストリーム
サーバー=Microsoft-IIS/5.0
Last-Modified=2001 年 4 月 30 日月曜日 12:56:11 GMT
いわゆるレジュームダウンロードとは、ファイルをダウンロードした時点からダウンロードを続けることを意味します。したがって、クライアントブラウザパスで
Web サーバーに関しては、どこから始めるべきかという情報をもう 1 つ追加する必要があります。
以下はリクエスト情報をWebサーバーに渡すために私がコンパイルした「ブラウザ」です。リクエストは2000070バイトから始まります。
/down.zip HTTP/1.0 を取得
ユーザーエージェント: NetFox
範囲: バイト=2000070-
受け入れる: text/html、image/gif、image/jpeg、*; q=.2、*/*;
よく見ると、RANGE という余分な行が見つかります: bytes=2000070-
この行の意味は、ファイル down.zip が 2000070 バイトから送信され、それより前のバイトを送信する必要がないことをサーバーに伝えることです。
サーバーがこのリクエストを受信すると、次のような情報が返されます。
206
コンテンツの長さ=106786028
Content-Range=バイト 2000070-106786027/106786028
日付=2001年4月30日月曜日12:55:20 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=アプリケーション/オクテットストリーム
サーバー=Microsoft-IIS/5.0
Last-Modified=月曜日、2001 年 4 月 30 日 12:55:20 GMT
以前のサーバーから返された情報と比較すると、次の行が追加されていることがわかります。
Content-Range=バイト 2000070-106786027/106786028
戻りコードも 200 ではなく 206 に変更されました。
上記の原則を理解すると、ブレークポイントの再開ダウンロードをプログラムできます。
(2) Javaでブレークポイント・レジューム転送を実装する際のポイント
(1) RANGE を送信するために使用される方法: bytes=2000070-。
もちろん、オリジナルの Socket を使用して実行することもできますが、それは面倒です。実際には、Java の net パッケージがこの機能を提供します。コードは次のとおりです。
URL url = 新しい URL("http://www.sjtu.edu.cn/down.zip");
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();
// ユーザーエージェントを設定する
httpConnection.setRequestProperty("ユーザーエージェント","NetFox");
//ブレークポイントレジューム送信の開始位置を設定
httpConnection.setRequestProperty("RANGE","bytes=2000070");
//入力ストリームを取得する
入力ストリーム入力 = httpConnection.getInputStream();
入力ストリームから取り出されるバイトストリームは、down.zipファイル内の2000070から始まるバイトストリームです。
ご存知のとおり、Java でブレークポイント再開転送を実装するのは実際には非常に簡単です。
次に、取得したストリームをファイルに保存する方法です。
ファイルの保存に使用される方法。
IO パッケージの RandAccessFile クラスを使用します。
操作は非常に簡単です。ファイルが 2000070 から保存されるとします。コードは次のとおりです。
次のようにコードをコピーします。
RandomAccess oSavedFile = new RandomAccessFile("down.zip","rw");
長い nPos = 2000070;
// ファイル ポインタを nPos 位置に配置します
oSavedFile.seek(nPos);
byte[] b = 新しいバイト[1024];
int nRead;
// 入力ストリームからバイト ストリームを読み取り、ファイルに書き込みます
while((nRead=input.read(b,0,1024)) > 0)
{
oSavedFile.write(b,0,nRead);
}
とにかく、とてもシンプルです。
次に行うことは、それを完全なプログラムに統合することです。一連のスレッド制御などを含みます。
(3) ブレークポイント・レジューム・カーネルの実装では<BR>テストクラスを含む主に6クラスを使用します。
SiteFileFetch.java は、ファイル全体を取得し、内部スレッド (FileSplitterFetch クラス) を制御します。
FileSplitterFetch.java は、いくつかのファイルをフェッチする役割を果たします。
FileAccess.java はファイルのストレージを担当します。
SiteInfoBean.java がクロールするファイルに関する情報 (ファイルが保存されているディレクトリ、その名前、クロールされたファイルの URL など)。
Utility.java ツール クラスに、いくつかの簡単なメソッドを追加します。
TestMethod.java テスト クラス。
以下はソースプログラムです。
次のようにコードをコピーします。
/*
**SiteFileFetch.java
*/
NetFox パッケージ。
java.io.* をインポートします。
java.net.* をインポートします。
パブリック クラス SiteFileFetch extends Thread {
SiteInfoBean siteInfoBean = null; // ファイル情報 Bean
long[] nStartPos //開始位置
long[] nEndPos; //終了位置
FileSplitterFetch[] fileSplitterFetch; // サブスレッド オブジェクト;
long nFileLength //ファイルの長さ;
boolean bFirst = true //初めてファイルを取得するかどうか。
boolean bStop = false;
File tmpFile //ファイルダウンロード用の一時情報
DataOutputStream 出力; // ストリームをファイルに出力します。
public SiteFileFetch(SiteInfoBean Bean) が IOException をスローする
{
siteInfoBean = Bean;
//tmpFile = File.createTempFile ("zhong","1111",new File(bean.getSFilePath()));
tmpFile = new File(bean.getSFilePath()+File.separator + bean.getSFileName()+".info");
if(tmpFile.exists ())
{
bFirst = false;
read_nPos();
}
それ以外
{
nStartPos = 新しいlong[bean.getNSplitter()];
nEndPos = 新しいlong[bean.getNSplitter()];
}
}
public void run()
{
//ファイルの長さを取得する
//ファイルを分割する
// インスタンス FileSplitterFetch
//FileSplitterFetch スレッドを開始します
// 子スレッドが戻るのを待ちます
試す{
if(bFirst)
{
nFileLength = getFileSize();
if(nFileLength == -1)
{
System.err.println("ファイルの長さが不明です!");
}
else if(nFileLength == -2)
{
System.err.println("ファイルにアクセスできません!");
}
それ以外
{
for(int i=0;i<nStartPos.length;i++)
{
nStartPos[i] = (long)(i*(nFileLength/nStartPos.length));
}
for(int i=0;i<nEndPos.length-1;i++)
{
nEndPos[i] = nStartPos[i+1];
}
nEndPos[nEndPos.length-1] = nFileLength;
}
}
// 子スレッドを開始します
fileSplitterFetch = 新しい FileSplitterFetch[nStartPos.length];
for(int i=0;i<nStartPos.length;i++)
{
fileSplitterFetch[i] = 新しい FileSplitterFetch(siteInfoBean.getSSiteURL(),
siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName()、
nStartPos[i],nEndPos[i],i);
Utility.log("スレッド " + i + " , nStartPos = " + nStartPos[i] + ", nEndPos = " + nEndPos[i]);
fileSplitterFetch[i].start();
}
// fileSplitterFetch[nPos.length-1] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),
siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(),nPos[nPos.length-1],nFileLength,nPos.length-1);
// Utility.log("スレッド " + (nPos.length-1) + " , nStartPos = " + nPos[nPos.length-1] + ",
nEndPos = " + nFileLength);
// fileSplitterFetch[nPos.length-1].start();
// 子スレッドが終了するまで待ちます
//int カウント = 0;
//whileループを終了するかどうか
ブール値 Breakwhile = false;
while(!bStop)
{
write_nPos();
Utility.sleep(500);
Breakwhile = true;
for(int i=0;i<nStartPos.length;i++)
{
if(!fileSplitterFetch[i].bDownOver)
{
Breakwhile = false;
壊す;
}
}
if(休憩中)
壊す;
//カウント++;
//if(カウント>4)
// サイトストップ();
}
System.err.println("ファイルのダウンロードが完了しました!");
}
catch(Exception e){e.printStackTrace ();}
}
//ファイルの長さを取得する
public long getFileSize()
{
int nFileLength = -1;
試す{
URL url = 新しい URL(siteInfoBean.getSSiteURL());
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
httpConnection.setRequestProperty("ユーザーエージェント","NetFox");
int responseCode=httpConnection.getResponseCode();
if(応答コード>=400)
{
プロセスエラーコード(応答コード);
return -2; //-2 はアクセスがエラーであることを表します
}
文字列 sHeader;
for(int i=1;;i++)
{
//DataInputStream in = new DataInputStream(httpConnection.getInputStream ());
//Utility.log(in.readLine());
sHeader=httpConnection.getHeaderFieldKey(i);
if(sHeader!=null)
{
if(sHeader.equals("コンテンツの長さ"))
{
nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));
壊す;
}
}
それ以外
壊す;
}
}
catch(IOException e){e.printStackTrace ();}
catch(Exception e){e.printStackTrace ();}
Utility.log(nFileLength);
nFileLength を返します。
}
//ダウンロード情報(ファイルポインタ位置)を保存
private void write_nPos()
{
試す{
出力 = 新しい DataOutputStream(新しい FileOutputStream(tmpFile));
Output.writeInt(nStartPos.length);
for(int i=0;i<nStartPos.length;i++)
{
// 出力.writeLong(nPos[i]);
Output.writeLong(fileSplitterFetch[i].nStartPos);
Output.writeLong(fileSplitterFetch[i].nEndPos);
}
出力.close();
}
catch(IOException e){e.printStackTrace ();}
catch(Exception e){e.printStackTrace ();}
}
//保存されているダウンロード情報(ファイルポインタの位置)を読み込む
private void read_nPos()
{
試す{
DataInputStream 入力 = new DataInputStream(new FileInputStream(tmpFile));
int nCount = input.readInt();
nStartPos = 新しいlong[nCount];
nEndPos = 新しいlong[nCount];
for(int i=0;i<nStartPos.length;i++)
{
nStartPos[i] = input.readLong();
nEndPos[i] = input.readLong();
}
input.close();
}
catch(IOException e){e.printStackTrace ();}
catch(Exception e){e.printStackTrace ();}
}
private void processErrorCode(int nErrorCode)
{
System.err.println("エラー コード : " + nErrorCode);
}
// ファイルのダウンロードを停止します
public void siteStop()
{
bStop = true;
for(int i=0;i<nStartPos.length;i++)
fileSplitterFetch[i].splitterStop();
}
}
/*
**FileSplitterFetch.java
*/
NetFox パッケージ。
java.io.* をインポートします。
java.net.* をインポートします。
public class FileSplitterFetch extends Thread {
文字列 sURL // ファイル URL;
long nStartPos; // ファイル スニペットの開始位置
long nEndPos; // ファイル スニペットの終了位置
int nThreadID //スレッドのID;
boolean bDownOver = false //ダウンは終了しました。
boolean bStop = false // 同一のストップ;
FileAccessI fileAccessI = null; // ファイル アクセス インターフェイス
public FileSplitterFetch(String sURL,String sName,long nStart,long nEnd,int id) が IOException をスローする
{
this.sURL = sURL;
this.nStartPos = nStart;
this.nEndPos = nEnd;
nThreadID = ID;
fileAccessI = 新しい FileAccessI(sName,nStartPos);
}
public void run()
{
while(nStartPos < nEndPos && !bStop)
{
試す{
URL url = 新しい URL(sURL);
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
httpConnection.setRequestProperty("ユーザーエージェント","NetFox");
文字列 sProperty = "bytes="+nStartPos+"-";
httpConnection.setRequestProperty("RANGE",sProperty);
Utility.log(sプロパティ);
入力ストリーム入力 = httpConnection.getInputStream();
//logResponseHead(httpConnection);
byte[] b = 新しいバイト[1024];
int nRead;
while((nRead=input.read(b,0,1024)) > 0 && nStartPos < nEndPos && !bStop)
{
nStartPos += fileAccessI.write(b,0,nRead);
//if(nThreadID == 1)
// Utility.log("nStartPos = " + nStartPos + ", nEndPos = " + nEndPos);
}
Utility.log("スレッド " + nThreadID + " は終了しました!");
bDownOver = true;
//nPos = fileAccessI.write (b,0,nRead);
}
catch(Exception e){e.printStackTrace ();}
}
}
// 応答ヘッダー情報を出力します
public void logResponseHead(HttpURLConnection con)
{
for(int i=1;;i++)
{
文字列ヘッダー = con.getHeaderFieldKey(i);
if(ヘッダー!=null)
//responseHeaders.put(header,httpConnection.getHeaderField(header));
Utility.log(header+" : "+con.getHeaderField(header));
それ以外
壊す;
}
}
public void spitterStop()
{
bStop = true;
}
}
/*
**ファイルアクセス.java
*/
NetFox パッケージ。
java.io.* をインポートします。
public class FileAccessI は Serializable{ を実装します
ランダムアクセスファイル o保存ファイル;
長い nPos;
public FileAccessI() が IOException をスローする
{
this("",0);
}
public FileAccessI(String sName,long nPos) が IOException をスローする
{
oSavedFile = new RandomAccessFile(sName,"rw");
this.nPos = nPos;
oSavedFile.seek(nPos);
}
public synchronized int write(byte[] b,int nStart,int nLen)
{
int n = -1;
試す{
oSavedFile.write(b,nStart,nLen);
n = nLen;
}
catch(IOException e)
{
e.printStackTrace();
}
n を返します。
}
}
/*
**SiteInfoBean.java
*/
NetFox パッケージ。
パブリック クラス SiteInfoBean {
private String sSiteURL // サイトの URL;
private String sFilePath; // 保存されたファイルのパス
private String sFileName // 保存されたファイルの名前
private int nSplitter //分割ダウンロードファイルの数
パブリック SiteInfoBean()
{
//nSplitter のデフォルト値は 5 です
this("","","",5);
}
public SiteInfoBean(String sURL,String sPath,String sName,int nSpiltter)
{
sSiteURL= sURL;
sFilePath = sPath;
sファイル名 = s名前;
this.nSplitter = nSplitter;
}
パブリック String getSSiteURL()
{
sSiteURL を返します。
}
public void setSSiteURL(文字列値)
{
sSiteURL = 値;
}
パブリック String getSFilePath()
{
sFilePathを返します。
}
public void setSFilePath(文字列値)
{
sFilePath = 値;
}
パブリック String getSFileName()
{
sFileName を返します。
}
public void setSFileName(文字列値)
{
sFileName = 値;
}
public int getNSplitter()
{
nSplitter を返します。
}
public void setNSplitter(int nCount)
{
nSplitter = nCount;
}
}
/*
**ユーティリティ.java
*/
NetFox パッケージ。
パブリック クラス ユーティリティ {
パブリックユーティリティ()
{
}
public static void sleep(int nSecond)
{
試す{
Thread.sleep(nSecond);
}
catch(例外 e)
{
e.printStackTrace();
}
}
public static void log(String sMsg)
{
System.err.println(sMsg);
}
public static void log(int sMsg)
{
System.err.println(sMsg);
}
}
/*
**テストメソッド.java
*/
NetFox パッケージ。
パブリック クラス TestMethod {
パブリック TestMethod()
{ ///xx/weblogic60b2_win.exe
試す{
SiteInfoBean Bean = new SiteInfoBean("http://localhost/xx/weblogic60b2_win.exe","L://temp","weblogic60b2_win.exe",5);
//SiteInfoBean Bean = new SiteInfoBean("http://localhost:8080/down.zip","L://temp","weblogic60b2_win.exe",5);
SiteFileFetch fileFetch = 新しい SiteFileFetch(bean);
fileFetch.start();
}
catch(Exception e){e.printStackTrace();
}
}
public static void main(String[] args)
{
新しいテストメソッド();
}
}