文件的上传下载(一)
2019獨角獸企業重金招聘Python工程師標準>>>
? ?最近公司培訓,所以收集整理了一些關于上傳下載的資料,進行了整理與大家分享。
? ?? ?Struts對文件上傳的支持非常好,它是通過jakarta comons filelupload庫的無縫集成而做到這一點的。(此次主要是基于struts1的上傳下載).
? ? ?
? ?
? ?
? ?
特別提示:要想使用html表單上傳一個或多個文件,須把表單的enctype屬性設置為multipart/form-data,把它的method屬性設置為post.
 
? ? ? ? 借助Apache項目下提供的commons-net-1.4.1.jar來實現對FTP的上傳下載的操作,實現對服務器上文件的操作。
 ? ??在實現ftp上傳下載前我們需要在本地搭建一個ftp的環境,本例采用的是Easy ?FTP Server構建ftp環境
 ? ? 我們還需要借助Apache項目下提供的commons-net-1.4.1.jar來實現對FTP的上傳下載的操作。
 應用:
 ? ? ? 我們可以通過ftpd的上傳下載實現文件跨服務器的遷移,應用自己的程序
 對服務器進行上傳,下載,修改,更新等操作。 
 
? ? ?
? ? ? ? 我們平常下載時有可能會出現斷網被迫中止,此時往往我們希望下載不是從頭開始的,我們就需要要到斷點續傳的功能,節約時間,提高客戶的體驗度。
 ? ? ?斷點續傳的原理
 其實斷點續傳的原理很簡單,就是在 Http 的請求上和一般的下載有所不同而已。?
 打個比方,瀏覽器請求服務器上的一個文時,所發出的請求如下:?
 假設服務器域名為 wwww.sjtu.edu.cn,文件名為 down.zip。?
 GET /down.zip HTTP/1.1?
 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-?
 excel, application/msword, application/vnd.ms-powerpoint, */*?
 Accept-Language: zh-cn?
 Accept-Encoding: gzip, deflate?
 User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)?
 Connection: Keep-Alive
 服務器收到請求后,按要求尋找請求的文件,提取文件的信息,然后返回給瀏覽器,返回信息如下:
 200?
 Content-Length=106786028?
 Accept-Ranges=bytes?
 Date=Mon, 30 Apr 2001 12:56:11 GMT?
 ETag=W/"02ca57e173c11:95b"?
 Content-Type=application/octet-stream?
 Server=Microsoft-IIS/5.0?
 Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT 
 所謂斷點續傳,也就是要從文件已經下載的地方開始繼續下載。所以在客戶端瀏覽器傳給 Web 服務器的時候要多加一條信息 -- 從哪里開始。?
 下面是用自己編的一個"瀏覽器"來傳遞請求信息給 Web 服務器,要求從 2000070 字節開始。?
 GET /down.zip HTTP/1.0?
 User-Agent: NetFox?
 RANGE: bytes=2000070-?
 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
 仔細看一下就會發現多了一行 RANGE: bytes=2000070-?
 這一行的意思就是告訴服務器 down.zip 這個文件從 2000070 字節開始傳,前面的字節不用傳了。?
 服務器收到這個請求以后,返回的信息如下:?
 206?
 Content-Length=106786028?
 Content-Range=bytes 2000070-106786027/106786028?
 Date=Mon, 30 Apr 2001 12:55:20 GMT?
 ETag=W/"02ca57e173c11:95b"?
 Content-Type=application/octet-stream?
 Server=Microsoft-IIS/5.0?
 Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT
 和前面服務器返回的信息比較一下,就會發現增加了一行:?
 Content-Range=bytes 2000070-106786027/106786028?
 返回的代碼也改為 206 了,而不再是 200 了。
package common.http;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
?* 一個多線程支持斷點續傳的工具類<br/>
?*/
public class HttpDownloader {
private final Log log = LogFactory.getLog(getClass().getName());
private int threads = 5; // 總共的線程數
private int maxThreads = 10; // 最大的線程數
private String destUrl; // 目標的URL
private String savePath; // 保存的路徑
private File lockFile;// 用來保存進度的文件
private String userAgent = "jHttpDownload";
private boolean useProxy = false;
private String proxyServer;
private int proxyPort;
private String proxyUser;
private String proxyPassword;
private int blockSize = 1024 * 4; // 4K 一個塊
// 1個位代表一個塊,,用來標記是否下載完成
private byte[] blockSet;
private int blockPage; // 每個線程負責的大小
private int blocks;
private boolean running; // 是否運行中,避免線程不能釋放
// =======下載進度信息
private long beginTime;
private AtomicLong downloaded = new AtomicLong(0); // 已下載的字節數\
private long fileLength; // 總的字節數
// 監控線程,用來保存進度和匯報進度
private MonitorThread monitorThread = new MonitorThread();
public HttpDownloader(String destUrl, String savePath, int threads) {
this.threads = threads;
this.destUrl = destUrl;
this.savePath = savePath;
}
public HttpDownloader(String destUrl, String savePath) {
this(destUrl, savePath, 5);
}
/**
* 開始下載
*/
public boolean download() {
log.info("下載文件" + destUrl + ",保存路徑=" + savePath);
beginTime = System.currentTimeMillis();//設置開始時間用于記錄下載所用的時間
boolean ok = false;
try {
File saveFile = new File(savePath);
lockFile = new File(savePath + ".lck");
if (lockFile.exists() && !lockFile.canWrite()) {
throw new Exception("文件被鎖住,或許已經在下載中了");
}
File parent = saveFile.getParentFile();
if (!parent.exists()) {
log.info("創建目錄=" + parent.getAbsolutePath());
}
if (!parent.canWrite()) {
throw new Exception("保存目錄不可寫");
}
if (saveFile.exists()) {
if (!saveFile.canWrite()) {
throw new Exception("保存文件不可寫,無法繼續下載");
}
log.info("檢查之前下載的文件");
if (lockFile.exists()) {
log.info("加載之前下載進度");
loadPrevious();
}
} else {
lockFile.createNewFile();
}
// 1初始化httpClient
HttpURLConnection httpUrlConnection = getHttpConnection(0);
String contentLength = httpUrlConnection
.getHeaderField("Content-Length");
if (contentLength != null) {
try {
fileLength = Long.parseLong(contentLength);
} catch (Exception e) {
}
}
log.info("下載文件的大小:" + fileLength);
if (fileLength <= 0) {
// 不支持多線程下載,采用單線程下載
log.info("服務器不能返回文件大小,采用單線程下載");
threads = 1;
}
if (httpUrlConnection.getHeaderField("Content-Range") == null) {
log.info("服務器不支持斷點續傳");
threads = 1;
} else {
log.info("服務器支持斷點續傳");
}
//
if (fileLength > 0 && parent.getFreeSpace() < fileLength) {
throw new Exception("磁盤空間不夠");
}
if (fileLength > 0) {
int i = (int) (fileLength / blockSize);
if (fileLength % blockSize > 0) {
i++;
}
blocks = i;
} else {
// 一個塊 ?
blocks = 1;
}
if (blockSet != null) {
log.info("檢查文件,是否能夠續傳");
if (blockSet.length != fileLength / (8l * blockSize)) {
log.info("文件大小已改變,需要重新下載");
blockSet = null;
}
}
if (blockSet == null) {
blockSet = BitUtil.createBit(blocks);
}
log.info("文件的塊數:" + blocks + "," + blockSet.length);
if (threads > maxThreads) {
log.info("超過最大線程數,使用最大線程數");
threads = maxThreads;
}
blockPage = blocks / threads; // 每個線程負責的塊數
log.info("分配線程。線程數量=" + threads + ",塊總數=" + blocks + ",總字節數="
+ fileLength + ",每塊大小=" + blockSize + ",塊/線程=" + blockPage);
// 檢查
running = true;
// 創建一個線程組,方便觀察和調試
ThreadGroup downloadGroup = new ThreadGroup("download");
for (int i = 0; i < threads; i++) {
int begin = i * blockPage;
int end = (i + 1) * blockPage;
if (i == threads - 1 && blocks % threads > 0) {
// 如果最后一個線程,有余數,需要修正
end = blocks;
}
// 掃描每個線程的塊是否有需要下載的
boolean needDownload = false;
for (int j = begin; j < end; j++) {
if (!BitUtil.getBit(blockSet, j)) {
needDownload = true;
break;
}
}
if (!needDownload) {
log.info("所有塊已經下載完畢.Begin=" + begin + ",End=" + end);
}
// 啟動下載其他線程
DownloadThread downloadThread = new DownloadThread(
downloadGroup, i, begin, end);
downloadThread.start();
}
monitorThread.setStop(false);
monitorThread.start();
// 也可以用Thread.join 實現等待進程完成
while (downloadGroup.activeCount() > 0) {
Thread.sleep(2000);
}
ok = true;
} catch (Exception e) {
e.printStackTrace();
log.error(e);
} finally {
// closeHttpClient();
}
if (ok) {
log.info("刪除進度文件:" + lockFile.getAbsolutePath());
lockFile.delete();
}
monitorThread.setStop(true);
log.info("下載完成,耗時:"
+ getTime((System.currentTimeMillis() - beginTime) / 1000));
return ok;
}
private void loadPrevious() throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
FileInputStream inStream = new FileInputStream(lockFile);
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = inStream.read(buffer))) {
outStream.write(buffer, 0, n);
}
outStream.close();
inStream.close();
blockSet = outStream.toByteArray();
log.info("之前的文件大小應該是:" + blockSet.length * 8l * blockSize + ",一共有:"
+ blockSet.length + "塊");
}
private HttpURLConnection httpConnection0; // 用來快速返回,減少一次連接
private HttpURLConnection getHttpConnection(long pos) throws IOException {
if (pos == 0 && httpConnection0 != null) {
return httpConnection0;
}
URL url = new URL(destUrl);
log.debug("開始一個Http請求連接。Url=" + url + "定位:" + pos + "\n");
// 默認的會處理302請求
HttpURLConnection.setFollowRedirects(false);
HttpURLConnection httpConnection = null;
if (useProxy) {
log.debug("使用代理進行連接.ProxyServer=" + proxyServer + ",ProxyPort="
+ proxyPort);
SocketAddress addr = new InetSocketAddress(proxyServer, proxyPort);
Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);
httpConnection = (HttpURLConnection) url.openConnection(proxy);
if (proxyUser != null && proxyPassword != null) {
String encoded = new String(Base64.encodeBase64(new String(
proxyUser + ":" + proxyPassword).getBytes()));
httpConnection.setRequestProperty("Proxy-Authorization",
"Basic " + encoded);
}
} else {
httpConnection = (HttpURLConnection) url.openConnection();
}
httpConnection.setRequestProperty("User-Agent", userAgent);
httpConnection.setRequestProperty("RANGE", "bytes=" + pos + "-");
int responseCode = httpConnection.getResponseCode();
log.debug("服務器返回:" + responseCode);
Map<String, List<String>> headers = httpConnection.getHeaderFields();
Iterator<String> iterator = headers.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String value = "";
for (String v : headers.get(key)) {
value = ";" + v;
}
log.debug(key + "=" + value);
}
if (responseCode < 200 || responseCode >= 400) {
throw new IOException("服務器返回無效信息:" + responseCode);
}
if (pos == 0) {
httpConnection0 = httpConnection;
}
return httpConnection;
}
public void returnConnection(HttpURLConnection connecton) {
if (connecton != null)
connecton.disconnect();
connecton = null;
}
private String getDesc() {
long downloadBytes = downloaded.longValue();
return String
.format(
"已下載/總大小=%s/%s(%s),速度:%s,耗時:%s,剩余大小:%d",
getFileSize(downloadBytes),
getFileSize(fileLength),
getProgress(fileLength, downloadBytes),
getFileSize(downloadBytes
/ ((System.currentTimeMillis() - beginTime) / 1000 + 1)),
getTime((System.currentTimeMillis() - beginTime) / 1000),
fileLength - downloadBytes);
}
private String getFileSize(long totals) {
// 計算文件大小
int i = 0;
String j = "BKMGT";
float s = totals;
while (s > 1024) {
s /= 1024;
i++;
}
return String.format("%.2f", s) + j.charAt(i);
}
private String getProgress(long totals, long read) {
if (totals == 0)
return "0%";
return String.format("%d", read * 100 / totals) + "%";
}
private String getTime(long seconds) {
int i = 0;
String j = "秒分時天";
long s = seconds;
String result = "";
while (s > 0) {
if (s % 60 > 0) {
result = String.valueOf(s % 60) + (char) j.charAt(i) + result;
}
s /= 60;
i++;
}
return result;
}
/**
* 一個下載線程.
*/
private class DownloadThread extends Thread {
private RandomAccessFile destFile; // 用來實現保存的隨機文件
private int id = 0;
private int blockBegin = 0; // 開始塊
private int blockEnd = 0; // 結束塊
private long pos;// 絕對指針
private String getThreadName() {
return "DownloadThread-" + id + "=>";
}
public DownloadThread(ThreadGroup group, int id, int blockBegin,
int blockEnd) throws Exception {
super(group, "downloadThread-" + id);
this.id = id;
this.blockBegin = blockBegin;
this.blockEnd = blockEnd;
this.pos = 1l * blockBegin * blockSize; // 轉換為長整型
destFile = new RandomAccessFile(savePath, "rw");
}
public void run() {
BufferedInputStream inputStream = null;
try {
log.info(getThreadName() + "下載線程." + this.toString());
log.info(getThreadName() + ":定位文件位置.Pos=" + 1l * blockBegin
* blockSize);
destFile.seek(1l * blockBegin * blockSize);
log.info(getThreadName() + ":開始下載.[ " + blockBegin + " - "
+ blockEnd + "]");
HttpURLConnection httpConnection = getHttpConnection(pos);
inputStream = new BufferedInputStream(httpConnection
.getInputStream());
byte[] b = new byte[blockSize];
while (blockBegin < blockEnd) {
if (!running) {
log.info(getThreadName() + ":停止下載.當前塊:" + blockBegin);
return;
}
log.debug(getThreadName() + "下載塊=" + blockBegin);
int counts = 0; // 已下載字節數
if (BitUtil.getBit(blockSet, blockBegin)) {
log.debug(getThreadName() + ":塊下載已經完成=" + blockBegin);
destFile.skipBytes(blockSize);
int skips = 0;
while (skips < blockSize) {
skips += inputStream.skip(blockSize - skips);
}
downloaded.addAndGet(blockSize);
} else {
while (counts < blockSize) {
int read = inputStream.read(b, 0, blockSize
- counts);
if (read < 0)
break;
counts += read;
destFile.write(b, 0, read);
downloaded.addAndGet(read);
}
BitUtil.setBit(blockSet, blockBegin, true); // 標記已經下載完成
}
blockBegin++;
}
httpConnection.disconnect();
log.info(getThreadName() + "下載完成.");
return;
} catch (Exception e) {
log.error(getThreadName() + "下載錯誤:" + e.getMessage());
e.printStackTrace();
} finally {
try {
if (inputStream != null)
inputStream.close();
} catch (Exception te) {
log.error(te);
}
try {
if (destFile != null)
destFile.close();
} catch (Exception te) {
log.error(te);
}
}
}
}
// 監控線程,并保存進度,方便下次斷點續傳
private class MonitorThread extends Thread {
boolean stop = false;
public void setStop(boolean stop) {
this.stop = stop;
}
public void run() {
FileOutputStream saveStream = null;
try {
while (running && !stop) {
saveStream = new FileOutputStream(lockFile);
log.info(getDesc());
// 保存進度
saveStream.write(blockSet, 0, blockSet.length);
sleep(1000);
saveStream.close();
}
} catch (Exception e) {
e.printStackTrace();
log.error(e);
} finally {
}
}
}
// 用來操作位的工具
private static class BitUtil {
public static byte[] createBit(int len) {
int size = len / Byte.SIZE;
if (len % Byte.SIZE > 0) {
size++;
}
return new byte[size];
}
/** 取出某位,是0 還是1 */
public static boolean getBit(byte[] bits, int pos) {
int i = pos / Byte.SIZE;
int b = bits[i];
int j = pos % Byte.SIZE;
byte c = (byte) (0x80 >>> (j - 1));
return b == c;
}
/** 設置某位,是0 還是1 */
public static void setBit(byte[] bits, int pos, boolean flag) {
int i = pos / Byte.SIZE;
byte b = bits[i];
int j = pos % Byte.SIZE;
byte c = (byte) (0x80 >>> (j - 1));
if (flag) {
bits[i] = (byte) (b | c);
} else {
c = (byte) (0xFF ^ c);
bits[i] = (byte) (b & c);
}
}
}
}
轉載于:https://my.oschina.net/songhongxu/blog/156353
總結
以上是生活随笔為你收集整理的文件的上传下载(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: UNIX网络编程——进程间通信概述
- 下一篇: nginx配置学习
