HTTP 프로토콜을 통한 멀티스레드 다운로드
1. 기본 원리는 각 라인이 파일의 다른 위치에서 다운로드를 시작하고 마지막에 완전한 데이터를 합친다.2. 멀티스레드 다운로드를 사용하면 다운로드 속도가 빠르다.왜 그랬을까?이전에 나는 서버에서 한 라인으로 다운로드한 것을 잘 이해한다.즉 서버에 대응하는 나의 다운로드 라인이 존재한다는 것이다.이럴 때 나 혼자만 다운로드하는 것이 아니라 서버에 여러 개의 다운로드 라인이 동시에 존재하고 서버 자원을 다운로드하는 것이다.CPU의 경우 동시 실행이 불가능합니다.CPU는 공정하게 이 스레드들을 슬라이스로 나누어 번갈아 실행한다. a스레드는 10밀리초, b스레드는 10밀리초...본고의 이런 수법을 운용했다고 가정하면 나의 다운로드 응용은 서버 측의 임의의 여러 라인을 동시에 다운로드할 수 있다는 것을 의미한다(이론상).이 스레드 수가 50개라고 가정하면 본 응용 프로그램은 서버 CPU의 보살핌을 50배 이상 받을 것이다.그러나 결국 로컬 네트워크 속도의 제한을 받는다.3. 각 라인이 다운로드를 담당해야 하는 데이터 길이는'다운로드 데이터의 총 길이'를'다운로드에 참여한 라인의 총 수량'으로 나누어 계산할 수 있다.하지만 정제할 수 없는 상황을 고려해야 한다.만약 5개의 라인이 다운로드에 참여한다면 계산 공식은 int Block = 데이터 총 길이% 라인 수 = 0?10/3 : 10/3+1; (정제할 수 없으면 1을 더함) 4.데이터베이스 페이지 조회 형식입니다.모든 라인은 자신이 데이터의 어느 위치에서부터 다운로드를 시작하고 어느 위치까지 다운로드하는지 알아야 한다.우선, 각 라인에 id를 배치하고, id는 0에서 시작하며, 0 1 2 3...시작 위치: 라인 id 곱하기 각 라인이 다운로드하는 데이터 길이.끝 위치: 다음 라인의 시작 위치의 이전 위치입니다.예를 들어 int start Position = 스레드 id * 스레드당 다운로드된 데이터 길이 int end Position = (스레드 id + 1) * 스레드당 다운로드된 데이터 길이 -1; 5. HTTP 프로토콜의 Range 헤더는 파일의 어느 위치에서 다운로드를 시작하고 다운로드가 끝날 위치를 지정할 수 있습니다.단위는 1byte Range:bytes=2097152-4194304는 파일의 2M 위치에서 다운로드를 시작하고 4M 위치에서 다운로드를 끝낸다는 뜻이다. 만약에 Range가 파일을 읽을 5104389의 바이트 위치를 지정한다면 다운로드한 파일 자체는 4104389개 길이에 불과하다.그러면 다운로드 작업은 자동으로 4104389곳에서 멈춘다.따라서 불필요한 무효 데이터를 다운로드하지 않습니다. 6.또 다른 난제는 데이터를 로컬 파일로 순서대로 쓰는 것이다.스레드는 로컬 대상 파일에 동시에 데이터를 쓰기 때문에 동기화됩니다.스레드가 스레드 사이에 쓴 데이터는 다운로드 데이터 자체의 순서에 따라 작성되지 않았다.일반적인 OutputStream 쓰기 방식을 사용하면 마지막 로컬 다운로드 파일이 올바르지 않습니다.그래서 우리는 다음과 같은 종류를 사용할 것이다:java.io.RandomAccessFile은 Data Output과 Data Input을 동시에 실현하는 방법이기 때문입니다.쓰기 및 읽기 기능을 모두 제공합니다.이 클래스는 마치 파일 바늘과 같은 것이 존재하는 것 같아서 파일의 어느 위치에서든 읽기와 쓰기를 시작할 수 있다.따라서 이러한 실례는 무작위 접근 파일에 대한 읽기와 쓰기를 지원합니다.예를 들면 다음과 같습니다.
File file = new File("1.txt");
RandomAccessFile accessFile = new RandomAccessFile(file,"rwd");
accessFile.setLength(1024);
비록 이 코드를 실행한 후에 우리는 아직 목표 파일 "에1.txt" 모든 데이터를 기록합니다.하지만 이때 크기를 보면 1kb입니다.이것은 우리가 스스로 설정한 크기다.이 동작은 이 파일에 대형byte 그룹을 저장하는 것과 유사합니다.이 그룹은 이 파일을 지정한 크기까지 버팀목합니다.대기가 가득 차다.이왕 이렇게 된 이상, 우리는 '인덱스' 를 통해 이 파일 시스템의 어떤 부분에 무작위로 접근할 수 있다는 장점이 있다.예를 들어 이 파일의 크기가 500일 수도 있습니다. 그러면 제 업무 수요는 처음으로 300위치에서 데이터를 써서 350까지 써야 할 수도 있습니다.두 번째로 나는 또 50부터 데이터를 써서 100까지 썼다.어쨌든 나는 이 서류를 일회성으로 순서대로 다 쓴 것이 아니다.그러면 RandomAccessFile에서 이러한 작업을 지원할 수 있습니다. API void setLength(long newLength) Sets the length of this file. (파일의 예상 크기 설정)void seek(long pos) Sets the file-pointer offset, measured from the beginning of this file,at which the next read or write occurs.이 방법으로 1028 인자를 전송한다고 가정하면 파일의 1028 위치부터 쓰기 시작합니다. void write(byte[] b, int off, int len) Writes len bytes from the specified byte array starting at offset off to this file. write(byte[] b) Writes b.length bytes from the specified byte array to this file, starting at the current file pointer. void writeUTF(String str) Writes a string to the file using modified UTF-8 encoding in a machine-independent manner. String readLine() Reads the next line of text from this file. 실험 코드:
public static void main(String[] args) throws Exception {
File file = new File("1.txt");
RandomAccessFile accessFile = new RandomAccessFile(file,"rwd");
/* 3 */
accessFile.setLength(3);
/* '2' */
accessFile.seek(1);
accessFile.write("2".getBytes());
/* '1' */
accessFile.seek(0); accessFile.write("1".getBytes());
/* '3' */
accessFile.seek(2);
accessFile.write("3".getBytes()); accessFile.close();
// :123
}
이상의 실험은 성공했습니다. 비록 우리가 문자열을 쓰는 순서는 "이지만.2"、"1"、"3",그러나 파일의 편이량을 설정했기 때문에 파일에 최종적으로 저장된 데이터는 123이다. 또 다른 의문은 이 세 가지 데이터를 다 썼을 때 파일의 크기는 이미 세 바이트 크기가 되었다.이미 쓴 데이터가 가득 찼으니, 우리가 계속 안에 데이터를 넣으면 어떤 효과가 있을까?/* 크기를 초과한 네 번째 바이트에 데이터를 쓰기 */accessFile.seek(3); accessFile.write("400".getBytes());상기 코드는 seek 방법이 지정한 파일 포인터의 편이량과 저장된 데이터를 막론하고 파일에 처음 설정한 3바이트의 크기를 초과했습니다.내 추측에 의하면 적어도 "accessFile.seek(3)"위치에서 "을 던질 것이다.ArrayIndexOutOfBoundsException" 이상, 단독 집행 "accessFile.write("400".getBytes())" 성공할 수 있을 거야.이 수요는 합리적이기 때문에 그것을 집행하는 메커니즘이 있어야 한다.실험 결과는 두 개의 코드가 모두 성공한 것이다.파일에 숨겨진 대형 바이트 그룹이 자동으로 커질 수 있다는 뜻인 것 같다.그러나 주의해야 할 문제는 설정된 파일 크기의 모든 위치가 합법적인 데이터를 가지고 있어야 하며 적어도 비어서는 안 된다는 것이다.예를 들어:/* 세 번째 위치에 '3' */accessFile을 씁니다.seek(2); accessFile.write("3".getBytes()); accessFile.seek(5); accessFile.write("400".getBytes());그러면 이전의 코드를 결합한 결과 123구 400이 공백의 두 위치에서 부호화되었다.이것은 당연한 것이다.또한 파일에 100개의 길이를 지정했다면accessFile.setLength(100);실제로 우리는 그 다섯 개의 위치에 값만 설정했다.당연히 파일에 저장된 데이터는 마지막에 95개의 난자가 붙는다. 7. 준비 작업은 충분히 되었을 것이다.다음은 코드입니다.
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
*
*/
public class MulThreadDownload {
/* URL */
private URL downloadUrl;
/* */
private File localFile;
/* */
private int block;
public static void main(String[] args) {
/* */
String downPath = "http://192.168.1.102:8080/myvideoweb/down.avi";
MulThreadDownload threadDownload = new MulThreadDownload();
/* 10 */
try {
threadDownload.download(downPath, 10);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
*
* @param path
* @param threadCount
*/
public void download(String path, int threadCount) throws Exception {
downloadUrl = new URL(path);
HttpURLConnection conn = (HttpURLConnection) downloadUrl
.openConnection();
/* GET */
conn.setRequestMethod("GET");
/* 5 */
conn.setConnectTimeout(5 * 1000);
/* */
String filename = parseFilename(path);
/* */
int dataLen = conn.getContentLength();
if (dataLen < 0) {
System.out.println(" ");
return;
}
/* , */
localFile = new File(filename);
RandomAccessFile accessFile = new RandomAccessFile(localFile, "rwd");
/* , , */
accessFile.setLength(dataLen);
accessFile.close();
/* */
block = dataLen % threadCount == 0 ? dataLen / threadCount : dataLen / threadCount + 1;
/* */
for (int i = 0; i < threadCount; i++) {
new DownloadThread(i).start();
}
}
/**
*
*/
private String parseFilename(String path) {
return path.substring(path.lastIndexOf("/") + 1);
}
/**
* :
*/
private final class DownloadThread extends Thread {
/* id */
private int threadid;
/* */
private int startPosition;
/* */
private int endPosition;
/**
*
* @param threadid id
*/
public DownloadThread(int threadid) {
this.threadid = threadid;
startPosition = threadid * block;
endPosition = (threadid + 1) * block - 1;
}
@Override
public void run() {
System.out.println(" '" + threadid + "' ..");
RandomAccessFile accessFile = null;
try {
/* ,"rwd" */
accessFile = new RandomAccessFile(localFile, "rwd");
accessFile.seek(startPosition);
HttpURLConnection conn = (HttpURLConnection) downloadUrl.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(5 * 1000);
/* HTTP Range , */
conn.setRequestProperty("Range", "bytes=" + startPosition + "-"
+ endPosition);
/* */
writeTo(accessFile, conn);
System.out.println(" '" + threadid + "' ");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(accessFile != null) {
accessFile.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
/**
*
*/
private void writeTo(RandomAccessFile accessFile,
HttpURLConnection conn){
InputStream is = null;
try {
is = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = is.read(buffer)) != -1) {
accessFile.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(is != null) {
is.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Java 다중 스레드를 순차적으로 실행하는 몇 가지 방법 요약Java 다중 스레드를 순차적으로 실행하는 몇 가지 방법 요약 동료는 무심결에 이 문제를 제기하고 두 가지 방법을 직접 실천했다.물론 더 좋은 방법이 있을 거야. 방법 1 이런 방법은 비교적 흔히 볼 수 있는 해결 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.