java兩臺服務器之間,大文件上傳(續傳),採用了Socket通訊機制以及JavaIO流兩個技術點,具體思路以下:html
實現思路:
一、服:利用ServerSocket搭建服務器,開啓相應端口,進行長鏈接操做
二、服:使用ServerSocket.accept()方法進行阻塞,接收客戶端請求
三、服:每接收到一個Socket就創建一個新的線程來處理它
四、客:利用Socket進行遠程鏈接,詢問已上傳進度
五、客:使用FileInputStream.skip(long length)從指定位置讀取文件,向服務器發送文件流
六、服:接收客戶端輸入流,使用RandomAccessFile.seek(long length)隨機讀取,將遊標移動到指定位置進行讀寫
七、客/服:一個循環輸出,一個循環讀取寫入
八、示例:如下是具體代碼,僅供參考
文件介紹:
FileUpLoadServer.java(服務器接收文件類)
FileUpLoadClient.java(客戶端發送文件類)
FinalVariables.java(自定義參數類)
SocketServerListener.java(JavaWeb啓動Socket操做類)
web.xml(配置文件,跟隨項目啓動)
斷點上傳(服務端)java
package com.cn.csdn.seesun2012.socket;git
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.RoundingMode;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DecimalFormat;web
public class FileUpLoadServer extends ServerSocket {服務器
// 文件大小
private static DecimalFormat df = null;
// 退出標識
private boolean quit = false;
static {
// 設置數字格式,保留一位有效小數
df = new DecimalFormat("#0.0");
df.setRoundingMode(RoundingMode.HALF_UP);
df.setMinimumFractionDigits(1);
df.setMaximumFractionDigits(1);
}
public FileUpLoadServer(int report) throws IOException {
super(report);
}
/**
* 使用線程處理每一個客戶端傳輸的文件
*
* @throws Exception
*/
public void load() throws Exception {
System.out.println("【文件上傳】服務器:" + this.getInetAddress() + " 正在運行中...");
while (!quit) {
// server嘗試接收其餘Socket的鏈接請求,server的accept方法是阻塞式的
Socket socket = this.accept();
/**
* 咱們的服務端處理客戶端的鏈接請求是同步進行的, 每次接收到來自客戶端的鏈接請求後,
* 都要先跟當前的客戶端通訊完以後才能再處理下一個鏈接請求。 這在併發比較多的狀況下會嚴重影響程序的性能,
* 爲此,咱們能夠把它改成以下這種異步處理與客戶端通訊的方式
*/
// 收到請求,驗證合法性
String ip = socket.getInetAddress().toString();
ip = ip.substring(1, ip.length());
System.out.println("服務器接收到請求,正在開啓驗證對方合法性IP:" + ip + "!");
// 每接收到一個Socket就創建一個新的線程來處理它
new Thread(new Task(socket, ip)).start();
}
}
/**
* 處理客戶端傳輸過來的文件線程類
*/
class Task implements Runnable {併發
private Socket sk; // 當前鏈接
private String ips; // 當前鏈接IP址app
public Task(Socket socket, String ip) {
this.sk = socket;
this.ips = ip;
}dom
public void run() {
Socket socket = sk; // 從新定義,請不要移出run()方法外部,不然鏈接兩會被重置
String ip = ips; // 從新定義,同上IP會變
long serverLength = -1l; // 定義:存放在服務器裏的文件長度,默認沒有爲-1
char pathChar = File.separatorChar; // 獲取:系統路徑分隔符
String panFu = "D:"; // 路徑:存儲文件盤符
DataInputStream dis = null; // 獲取:客戶端輸出流
DataOutputStream dos = null; // 發送:向客戶端輸入流
FileOutputStream fos = null; // 讀取:服務器本地文件流
RandomAccessFile rantmpfile = null; // 操做類:隨機讀取
try {
// 獲取
dis = new DataInputStream(socket.getInputStream());
// 發送
dos = new DataOutputStream(socket.getOutputStream());
// 定義客戶端傳過來的文件名
String fileName = "";
while (fileName == "") {
// 讀取客戶端傳來的數據
fileName = dis.readUTF();
System.out.println("服務器獲取客戶端文件名稱:" + fileName);
File file = new File(panFu+ pathChar +"receive"+ pathChar +"" + ip + pathChar + fileName);
if (file.exists()) {
serverLength = file.length();
dos.writeLong(serverLength);
System.out.println("向客戶端返回文件長度:" + serverLength + " B");
} else {
serverLength = 0l;
dos.writeLong(serverLength);
System.out.println("文件不存在");
System.out.println("向客戶端返回文件長度:" + serverLength + " B");
}
}
System.out.println("服務器創建新線程處理客戶端請求,對方IP:" + ip + ",傳輸正在進行中...");
// 從客戶端獲取輸入流
dis = new DataInputStream(socket.getInputStream());
// 文件名和長度
long fileLength = dis.readLong();
File directory = new File(panFu + pathChar + "receive"+ pathChar +"" + ip + pathChar);
if (!directory.exists()) {
directory.mkdirs();
}
int length = 0;
byte[] bytes = new byte[1024];
File file = new File(directory.getAbsolutePath() + pathChar + fileName);
if (!file.exists()) {
// 不存在
fos = new FileOutputStream(file);
// 開始接收文件
while ((length = dis.read(bytes, 0, bytes.length)) != -1) {
fos.write(bytes, 0, length);
fos.flush();
}
} else {
// 存儲在服務器中的文件長度
long fileSize = file.length(), pointSize = 0;
// 判斷是否已下載完成
if (fileLength > fileSize) {
// 斷點下載
pointSize = fileSize;
} else {
// 從新下載
file.delete();
file.createNewFile();
}異步
rantmpfile = new RandomAccessFile(file, "rw");
/*
* java.io.InputStream.skip() 用法:跳過 n 個字節(丟棄) 若是 n
* 爲負,則不跳過任何字節。
*/
// dis.skip(pointSize); (已從客戶端讀取進度)
/**
* 資源,文件定位(遊標、指針) 將ras的指針設置到8,則讀寫ras是從第9個字節讀寫到
*/
rantmpfile.seek(pointSize);socket
while ((length = dis.read(bytes, 0, bytes.length)) != -1) {
rantmpfile.write(bytes, 0, length);
}
}
System.out.println("======== 文件接收成功 [File Name:" + fileName + "] [ClientIP:" + ip + "] [Size:" + getFormatFileSize(file.length()) + "] ========");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null)
fos.close();
if (dis != null)
dis.close();
if (rantmpfile != null)
rantmpfile.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
System.out.println("Socket關閉失敗!");
}
/**
* 文件傳輸完畢:執行後續操做(略)
*/
//DoSomeThing dst = new DoSomeThing()
//dst.save(filePath);
}
}
}
/**
* 格式化文件大小
*
* @param length
* @return
*/
public String getFormatFileSize(long length) {
double size = ((double) length) / (1 << 30);
if (size >= 1) {
return df.format(size) + "GB";
}
size = ((double) length) / (1 << 20);
if (size >= 1) {
return df.format(size) + "MB";
}
size = ((double) length) / (1 << 10);
if (size >= 1) {
return df.format(size) + "KB";
}
return length + "B";
}
/**
* 退出
*/
public void quit() {
this.quit = true;
try {
this.close();
} catch (IOException e) {
System.out.println("服務器關閉發生異常,緣由未知");
}
}
}
斷點上傳(客戶端)
package com.cn.csdn.seesun2012.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Client端<br><br>
* 功能說明:文件上傳(斷點)傳輸
*
* @author CSDN:seesun2012
* @CreateDate 2017年08月18日
* @Override 2017年11月07日
* @version 1.1
*/
public class FileUpLoadClient extends Socket{
private Logger logger = LoggerFactory.getLogger("oaLogger");
private Socket client; // Socket-客戶端
private static long status = 0; // 進度條
private boolean quit = false; //退出
/**
* 構造器
*
* @param ip 服務端IP地址
* @param report 服務端開放的端口
* @throws UnknownHostException
* @throws IOException
*/
public FileUpLoadClient(String ip, Integer report) throws UnknownHostException, IOException {
super(ip, report);
this.client = this;
if (client.getLocalPort()>0) {
System.out.println("Cliect[port:" + client.getLocalPort() + "] 成功鏈接服務端");
}else{
System.out.println("服務器鏈接失敗");
}
}
public int sendFile(String filePath) {
DataOutputStream dos = null; // 上傳服務器:輸出流
DataInputStream dis = null; // 獲取服務器:輸入流
Long serverLength = -1l; // 存儲在服務器的文件長度,默認-1
FileInputStream fis = null; // 讀取文件:輸入流
// 獲取:上傳文件
File file = new File(filePath);
// ==================== 節點:文件是否存在 ====================
if (file.exists()) {
// 發送:文件名稱、文件長度
try {
dos = new DataOutputStream(client.getOutputStream());
} catch (IOException e2) {
logger.error("Socket客戶端:1.讀取輸出流發生錯誤");
e2.printStackTrace();
}
try {
dos.writeUTF(file.getName());
dos.flush();
dos.writeLong(file.length());
dos.flush();
} catch (IOException e2) {
logger.error("Socket客戶端:2.向服務器發送文件名、長度發生錯誤");
e2.printStackTrace();
}
// 獲取:已上傳文件長度
try {
dis = new DataInputStream(client.getInputStream());
} catch (IOException e2) {
logger.error("Socket客戶端:3.向服務器發送文件名、長度發生錯誤");
e2.printStackTrace();
}
while(serverLength==-1){
try {
serverLength = dis.readLong();
} catch (IOException e) {
logger.error("Socket客戶端:4.讀取服務端長度發送錯誤");
e.printStackTrace();
}
}
// 讀取:須要上傳的文件
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e2) {
logger.error("Socket客戶端:5.讀取本地須要上傳的文件失敗,請確認文件是否存在");
e2.printStackTrace();
}
// 發送:向服務器傳輸輸入流
try {
dos = new DataOutputStream(client.getOutputStream());
} catch (IOException e2) {
logger.error("Socket客戶端:6.向服務器傳輸輸入流發生錯誤");
e2.printStackTrace();
}
System.out.println("======== 開始傳輸文件 ========");
byte[] bytes = new byte[1024];
int length = 1024;
long progress = serverLength;
// 設置遊標:文件讀取的位置
if (serverLength==-1l) {
serverLength = 0l;
}
try {
fis.skip(serverLength);
} catch (IOException e1) {
logger.error("Socket客戶端:7.設置遊標位置發生錯誤,請確認文件流是否被篡改");
e1.printStackTrace();
}
try {
while (((length = fis.read(bytes, 0, bytes.length)) != -1) && quit != true) {
dos.write(bytes, 0, length);
dos.flush();
progress += length;
status = (100 * progress / file.length());
}
} catch (IOException e) {
logger.error("Socket客戶端:8.設置遊標位置發生錯誤,請確認文件流是否被篡改");
e.printStackTrace();
}finally {
if (fis != null)
try {
fis.close();
} catch (IOException e1) {
logger.error("Socket客戶端:9.關閉讀取的輸入流異常");
e1.printStackTrace();
}
if (dos != null)
try {
dos.close();
} catch (IOException e1) {
logger.error("Socket客戶端:10.關閉發送的輸出流異常");
e1.printStackTrace();
}
try {
client.close();
} catch (IOException e) {
logger.error("Socket客戶端:11.關閉客戶端異常");
e.printStackTrace();
}
}
System.out.println("======== 文件傳輸成功 ========");
}else{
logger.error("Socket客戶端:0.文件不存在");
return -1;
}
return 1;
}
/**
* 進度條
*/
public void statusInfo(){
Timer time = new Timer();
time.schedule(new TimerTask() {
long num = 0;
@Override
public void run() {
if (status>num) {
System.out.println("當前進度爲:"+status+"%");
num = status;
}
if (status==101) {
System.gc();
}
}
},0,100);
}
/**
* 退出
*/
public void quit() {
this.quit = true;
try {
this.close();
} catch (IOException e) {
System.out.println("服務器關閉發生異常,緣由未知");
}
}
}
斷點上傳(參數設置)
package com.cn.csdn.seesun2012.socket;
public interface FinalVariables {
// 服務端IP
public final static String SERVER_IP = "192.168.1.10010";
// 服務端端口
public final static int SERVER_PORT = 10086;
// 開啓配置
public final static String IS_START_SERVER = "instart";
}
斷點上傳(JavaWeb啓動服務端)
package com.cn.csdn.seesun2012.socket;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
/**
* Server端<br><br>
* 功能說明:服務端監聽開啓Servlet
*
* @author CSDN:seesun2012
* @CreateDate 2017年08月18日
* @Override 2017年11月07日
* @Override 2017年11月14日
* @version 1.3
*/
public class SocketServerListener extends HttpServlet{
private static final long serialVersionUID = -999999999999999999L;
// 初始化啓動Socket服務
@Override
public void init() throws ServletException {
super.init();
for(int i = 0; i < 3; i++){
if ("instart".equals(FinalVariables.IS_START_SERVER )) {
open();
break;
}
}
}
public void open(){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@SuppressWarnings("resource")
@Override
public void run() {
try {
FileUpLoadServer fileUpLoadServer = new FileUpLoadServer(FinalVariables.SERVER_PORT);
fileUpLoadServer.load();
} catch (Exception e) {
e.printStackTrace();
}
}
}, 3000);
}
}
web.xml配置(跟隨項目啓動)
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>SocketServerListener</servlet-name>
<servlet-class>com.cn.csdn.seesun2012.socket.SocketServerListener</servlet-class>
<load-on-startup>10</load-on-startup>
</servlet>
<display-name>seesun2012</display-name>
</web-app>
功能展現截圖:
在頁面中選擇好相應的上傳目錄,點擊粘貼上傳按鈕,數據便可快速開始上傳,電腦重啓後打開網頁也能夠按當前進度繼續上傳
文件和目錄下載
批量下載 同時選擇多個須要下載的文件 而後點擊下載按鈕,設置下載目錄文件夾
詳細的配置信息能夠參考我寫的這篇文章:http://blog.ncmem.com/wordpress/2019/08/09/java%e5%a4%a7%e6%96%87%e4%bb%b6%e4%b8%8b%e8%bd%bd%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/