(1) 재개 가능한 다운로드의 원칙 <BR>사실, 재개 가능한 다운로드의 원칙은 매우 간단합니다. 단지 HTTP 요청이 일반적인 다운로드와 다르다는 것뿐입니다.
예를 들어, 브라우저가 서버의 파일을 요청할 때 요청은 다음과 같습니다.
서버 도메인 이름이 wwww.sjtu.edu.cn이고 파일 이름이 down.zip이라고 가정합니다.
GET /down.zip HTTP/1.1
수락: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
엑셀, 애플리케이션/msword, 애플리케이션/vnd.ms-powerpoint, */*
허용 언어: zh-cn
Accept-Encoding: gzip, deflate
사용자 에이전트: Mozilla/4.0(호환 가능, MSIE 5.01, Windows NT 5.0)
연결: 연결 유지
서버는 요청을 받은 후 필요에 따라 요청된 파일을 검색하고 파일 정보를 추출한 후 브라우저에 반환합니다. 반환되는 정보는 다음과 같습니다.
200
콘텐츠 길이=106786028
허용 범위=바이트
날짜=2001년 4월 30일 월요일 12:56:11 GMT
ETag=W/"02ca57e173c11:95b"
콘텐츠 유형=응용 프로그램/옥텟-스트림
서버=Microsoft-IIS/5.0
최종 수정=2001년 4월 30일 월요일 12:56:11 GMT
소위 이력서 다운로드는 파일을 다운로드한 지점부터 계속 다운로드하는 것을 의미합니다. 따라서 클라이언트 브라우저에서 패스
웹 서버의 경우 시작 위치라는 정보를 하나 더 추가해야 합니다.
다음은 요청 정보를 웹 서버에 전달하기 위해 직접 컴파일한 "브라우저"입니다. 요청은 2000070바이트부터 시작됩니다.
GET /down.zip HTTP/1.0
사용자 에이전트: NetFox
범위: 바이트=2000070-
허용: 텍스트/html, 이미지/gif, 이미지/jpeg, *; q=.2, */*;
주의 깊게 살펴보면 RANGE라는 추가 줄을 찾을 수 있습니다: bytes=2000070-
이 줄의 의미는 down.zip 파일이 2000070바이트부터 전송되며 이전 바이트는 전송될 필요가 없음을 서버에 알리는 것입니다.
서버가 이 요청을 받은 후 반환되는 정보는 다음과 같습니다.
206
콘텐츠 길이=106786028
콘텐츠 범위=바이트 2000070-106786027/106786028
날짜=2001년 4월 30일 월요일 12:55:20 GMT
ETag=W/"02ca57e173c11:95b"
콘텐츠 유형=응용 프로그램/옥텟-스트림
서버=Microsoft-IIS/5.0
최종 수정=2001년 4월 30일 월요일 12:55:20 GMT
이전 서버에서 반환된 정보와 비교하면 다음 줄이 추가된 것을 알 수 있습니다.
콘텐츠 범위=바이트 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("User-Agent","NetFox");
//중단점 재개 전송의 시작 위치를 설정합니다.
httpConnection.setRequestProperty("RANGE","bytes=2000070");
//입력 스트림 얻기
InputStream 입력 = httpConnection.getInputStream();
입력 스트림에서 꺼낸 바이트 스트림은 down.zip 파일의 2000070부터 시작하는 바이트 스트림입니다.
실제로 Java에서 중단점 이력서 전송을 구현하는 것은 매우 간단합니다.
다음으로 할 일은 획득한 스트림을 파일에 저장하는 방법입니다.
파일을 저장하는 데 사용되는 방법입니다.
IO 패키지에서 RandAccessFile 클래스를 사용합니다.
작업은 매우 간단합니다. 파일이 2000070부터 저장되었다고 가정합니다. 코드는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다 .
RandomAccess oSavedFile = new RandomAccessFile("down.zip","rw");
긴 nPos = 2000070;
//nPos 위치에 대한 파일 포인터 찾기
oSavedFile.seek(nPos);
바이트[] 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는 파일 저장을 담당합니다.
파일이 저장된 디렉터리, 이름, 크롤링된 파일의 URL 등 SiteInfoBean.java가 크롤링하려는 파일에 대한 정보입니다.
Utility.java 도구 클래스에는 몇 가지 간단한 메소드를 추가했습니다.
TestMethod.java 테스트 클래스.
다음은 소스 프로그램입니다.
다음과 같이 코드 코드를 복사합니다 .
/*
**SiteFileFetch.java
*/
패키지 NetFox;
import java.io.*;
java.net.* 가져오기;
공개 클래스 SiteFileFetch는 Thread {를 확장합니다.
SiteInfoBean siteInfoBean = null; //파일 정보 Bean;
long[] nStartPos; //시작 위치
long[] nEndPos; //끝 위치
FileSplitterFetch[] fileSplitterFetch; //하위 스레드 객체
긴 nFileLength; //파일 길이
boolean bFirst = true; //처음으로 파일을 가져올지 여부
부울 bStop = false; 정지 신호
File tmpFile; //파일 다운로드를 위한 임시 정보
DataOutputStream 출력; //파일로 스트림 출력
공개 SiteFileFetch(SiteInfoBean bean)가 IOException을 발생시킵니다.
{
siteInfoBean = 빈;
//tmpFile = File.createTempFile ("zhong","1111",new File(bean.getSFilePath()));
tmpFile = new File(bean.getSFilePath()+File.separator + bean.getSFileName()+".info");
if(tmp파일.존재())
{
b첫 번째 = 거짓;
read_nPos();
}
또 다른
{
nStartPos = new long[bean.getNSplitter()];
nEndPos = new long[bean.getNSplitter()];
}
}
공개 무효 실행()
{
//파일 길이를 구한다
//파일 분할
//인스턴스 FileSplitterFetch
//FileSplitterFetch 스레드 시작
//자식 스레드가 반환될 때까지 기다립니다.
노력하다{
if(b첫번째)
{
nFileLength = getFileSize();
if(n파일 길이 == -1)
{
System.err.println("파일 길이를 알 수 없습니다!");
}
그렇지 않은 경우(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 = new 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();
//자식 스레드가 끝날 때까지 기다립니다.
//정수 = 0;
//while 루프를 종료할지 여부
부울 breakWhile = false;
동안(!b중지)
{
write_nPos();
Utility.sleep(500);
breakWhile = 사실;
for(int i=0;i<nStartPos.length;i++)
{
if(!fileSplitterFetch[i].bDownOver)
{
breakWhile = 거짓;
부서지다;
}
}
if(breakWhile)
부서지다;
//카운트++;
//if(개수>4)
// 사이트스톱();
}
System.err.println("파일 다운로드가 완료되었습니다!");
}
catch(예외 e){e.printStackTrace();}
}
//파일 길이를 구한다
공개 긴 getFileSize()
{
int nFileLength = -1;
노력하다{
URL url = 새 URL(siteInfoBean.getSSiteURL());
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
httpConnection.setRequestProperty("User-Agent","NetFox");
int responseCode=httpConnection.getResponseCode();
if(응답코드>=400)
{
processErrorCode(응답 코드);
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(예외 e){e.printStackTrace();}
Utility.log(nFileLength);
nFileLength를 반환합니다.
}
//다운로드 정보 저장(파일 포인터 위치)
개인 무효 write_nPos()
{
노력하다{
출력 = new DataOutputStream(new 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);
}
출력.닫기();
}
catch(IOException e){e.printStackTrace();}
catch(예외 e){e.printStackTrace();}
}
//저장된 다운로드 정보(파일 포인터 위치)를 읽어옵니다.
개인 무효 read_nPos()
{
노력하다{
DataInputStream 입력 = new DataInputStream(new FileInputStream(tmpFile));
int nCount = input.readInt();
nStartPos = new long[nCount];
nEndPos = new long[nCount];
for(int i=0;i<nStartPos.length;i++)
{
nStartPos[i] = input.readLong();
nEndPos[i] = input.readLong();
}
입력.닫기();
}
catch(IOException e){e.printStackTrace();}
catch(예외 e){e.printStackTrace();}
}
개인 무효 processErrorCode(int nErrorCode)
{
System.err.println("오류 코드 : " + nErrorCode);
}
//파일 다운로드 중지
공개 무효 siteStop()
{
b정지 = 참;
for(int i=0;i<nStartPos.length;i++)
fileSplitterFetch[i].splitterStop();
}
}
/*
**FileSplitterFetch.java
*/
패키지 NetFox;
import java.io.*;
java.net.* 가져오기;
공개 클래스 FileSplitterFetch는 Thread {를 확장합니다.
문자열 sURL; //파일 URL;
long nStartPos; //파일 조각 시작 위치
long nEndPos; //파일 조각 끝 위치
int nThreadID; //스레드의 ID
boolean bDownOver = false //다운이 끝났습니다.
부울 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 = 아이디;
fileAccessI = new FileAccessI(sName,nStartPos);
}
공개 무효 실행()
{
while(nStartPos < nEndPos && !bStop)
{
노력하다{
URL url = 새 URL(sURL);
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
httpConnection.setRequestProperty("User-Agent","NetFox");
문자열 sProperty = "bytes="+nStartPos+"-";
httpConnection.setRequestProperty("RANGE",sProperty);
Utility.log(sProperty);
InputStream 입력 = httpConnection.getInputStream();
//logResponseHead(httpConnection);
바이트[] 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("Thread " + nThreadID + "가 끝났습니다!");
bDownOver = 참;
//nPos = fileAccessI.write(b,0,nRead);
}
catch(예외 e){e.printStackTrace();}
}
}
//응답 헤더 정보 출력
공개 무효 logResponseHead(HttpURLConnection con)
{
for(int i=1;;i++)
{
문자열 헤더=con.getHeaderFieldKey(i);
if(헤더!=null)
//responseHeaders.put(header,httpConnection.getHeaderField(header));
Utility.log(header+" : "+con.getHeaderField(헤더));
또 다른
부서지다;
}
}
공공 무효 SplitterStop()
{
b정지 = 참;
}
}
/*
**파일액세스.java
*/
패키지 NetFox;
import java.io.*;
공개 클래스 FileAccessI는 직렬화 가능{을 구현합니다.
RandomAccessFile oSavedFile;
긴 nPos;
공개 FileAccessI()가 IOException을 발생시킵니다.
{
this("",0);
}
공용 FileAccessI(String sName,long nPos)에서 IOException이 발생합니다.
{
oSavedFile = new RandomAccessFile(sName,"rw");
this.nPos = nPos;
oSavedFile.seek(nPos);
}
공개 동기화 int write(byte[] b,int nStart,int nLen)
{
int n = -1;
노력하다{
oSavedFile.write(b,nStart,nLen);
n = nLen;
}
catch(IO예외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);
}
공개 SiteInfoBean(문자열 sURL, 문자열 sPath, 문자열 sName,int nSpiltter)
{
sSiteURL= sURL;
sFilePath = sPath;
s파일이름 = s이름;
this.nSplitter = nSpiltter;
}
공개 문자열 getSSiteURL()
{
sSiteURL을 반환합니다.
}
공개 무효 setSSiteURL(문자열 값)
{
sSiteURL = 값;
}
공개 문자열 getSFilePath()
{
sFilePath를 반환합니다.
}
공개 무효 setSFilePath(문자열 값)
{
sFilePath = 값;
}
공개 문자열 getSFileName()
{
sFileName을 반환합니다.
}
공개 무효 setSFileName(문자열 값)
{
s파일명 = 값;
}
공개 int getNSplitter()
{
nSplitter를 반환합니다.
}
공공 무효 setNSplitter(int nCount)
{
nSplitter = nCount;
}
}
/*
**Utility.java
*/
패키지 NetFox;
공개 클래스 유틸리티 {
공공 유틸리티()
{
}
공개 정적 무효 수면(int nSecond)
{
노력하다{
Thread.sleep(nSecond);
}
catch(예외e)
{
e.printStackTrace();
}
}
공개 정적 무효 로그(문자열 sMsg)
{
System.err.println(sMsg);
}
공개 정적 무효 로그(int sMsg)
{
System.err.println(sMsg);
}
}
/*
**TestMethod.java
*/
패키지 NetFox;
공개 클래스 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 = new SiteFileFetch(bean);
fileFetch.start();
}
catch(Exception e){e.printStackTrace();
}
}
공개 정적 무효 메인(문자열[] 인수)
{
새로운 테스트메소드();
}
}