這是一個簡單的C/S架構,基本實現思路是將服務器註冊至某個空閒端口用來監視並處理每一個客戶端的傳輸請求。java
客戶端先得到用戶給予的需傳輸文件與目標路徑,以後根據該文件實例化RandomAccessFile爲只讀,以後客戶端向服務器發送需傳輸的文件名文件大小與目標路徑,服務器沒接收到一個客戶端的請求就會創建一個新的線程去處理它,根據接收到的文件名到目標路徑中去尋找目標路徑中是否已經有該文件名的.temp臨時文件(若是沒有就建立它),以後服務器會將文件已經傳輸的大小(臨時文件大小)返回給客戶端(例如臨時文件剛剛創建返回的即是0),客戶端會將剛剛創建的RandomAccessFile對象的文件指針指向服務器返回的位置,以後以1kb爲一組向服務器傳輸需傳輸文件的內容數據,服務器則接收數據並將其寫入臨時文件中,並根據現有數據畫出進度條。在文件傳輸完畢後客戶端會將臨時文件重命名爲最初接收到的文件名。bash
服務器代碼:服務器
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.ServerSocket;
import java.net.Socket;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
public class FileTransferServer extends ServerSocket {
private static final int SERVER_PORT = 8899; // 服務端端口
public FileTransferServer() throws Exception {
super(SERVER_PORT);
}
public void load() throws Exception {
while (true) {
// server嘗試接收其餘Socket的鏈接請求,server的accept方法是阻塞式的
Socket socket = this.accept();
// 每接收到一個Socket就創建一個新的線程來處理它
new Thread(new Task(socket)).start();
}
}
//處理客戶端傳輸過來的文件線程類
class Task implements Runnable {
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private RandomAccessFile rad;
private JFrame frame; //用來顯示進度條
private Container contentPanel;
private JProgressBar progressbar;
private JLabel label;
public Task(Socket socket) {
frame = new JFrame("文件傳輸");
this.socket = socket;
}
@Override
public void run() {
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
String targetPath = dis.readUTF(); //接收目標路徑
String fileName = dis.readUTF(); //接收文件名
//System.out.println("服務器:接收文件名");
long fileLength = dis.readLong(); //接收文件長度
//System.out.println("服務器:接收文件長度");
File directory = new File(targetPath); //目標地址
if(!directory.exists()) { //目標地址文件夾不存在則建立該文件夾
directory.mkdir();
}
File file = new File(directory.getAbsolutePath() + File.separatorChar + fileName + ".temp"); //創建臨時數據文件.temp
//System.out.println("服務器:加載temp文件");
rad = new RandomAccessFile(directory.getAbsolutePath() + File.separatorChar + fileName + ".temp", "rw");
long size = 0;
if(file.exists() && file.isFile()){ //若是目標路徑存在且是文件,則獲取文件大小
size = file.length();
}
//System.out.println("服務器:獲的當前已接收長度");
dos.writeLong(size); //向客戶端發送當前數據文件大小
dos.flush();
//System.out.println("服務器:發送當前以接收文件長度");
int barSize = (int)(fileLength / 1024); //進度條當前進度
int barOffset = (int)(size / 1024); //進度條總長
frame.setSize(300,120); //傳輸界面
contentPanel = frame.getContentPane();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
progressbar = new JProgressBar(); //進度條
label = new JLabel(fileName + " 接收中");
contentPanel.add(label);
progressbar.setOrientation(JProgressBar.HORIZONTAL); //進度條爲水平
progressbar.setMinimum(0); //進度條最小值
progressbar.setMaximum(barSize); //進度條最大值
progressbar.setValue(barOffset); //進度條當前值
progressbar.setStringPainted(true); //顯示進度條信息
progressbar.setPreferredSize(new Dimension(150, 20)); //進度條大小
progressbar.setBorderPainted(true); //爲進度條繪製邊框
progressbar.setBackground(Color.pink); //進度條顏色爲騷粉
JButton cancel = new JButton("取消"); //取消按鈕
JPanel barPanel = new JPanel();
barPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
barPanel.add(progressbar);
barPanel.add(cancel);
contentPanel.add(barPanel);
cancel.addActionListener(new cancelActionListener());
//爲取消按鈕註冊監聽器
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
rad.seek(size); //移動文件指針
//System.out.println("服務器:文件定位完成");
int length;
byte[] bytes=new byte[1024];
while((length = dis.read(bytes, 0, bytes.length)) != -1){
rad.write(bytes,0, length); //寫入文件
progressbar.setValue(++barOffset); //更新進度條(因爲進度條每一個單位表明大小爲1kb,因此過小的文件就顯示不出啦)
}
if (barOffset >= barSize) { //傳輸完成後的重命名
if(rad != null)
rad.close();
if(!file.renameTo(new File(directory.getAbsolutePath() + File.separatorChar + fileName))) {
file.delete();
//防護性處理刪除臨時文件
}
//System.out.println("服務器:臨時文件重命名完成");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try { //關閉資源
if(rad != null)
rad.close();
if(dis != null)
dis.close();
if(dos != null)
dos.close();
frame.dispose();
socket.close();
} catch (Exception e) {}
}
}
class cancelActionListener implements ActionListener{ //取消按鈕監聽器
public void actionPerformed(ActionEvent e){
try {
//System.out.println("服務器:接收取消");
if(dis != null)
dis.close();
if(dos != null)
dos.close();
if(rad != null)
rad.close();
frame.dispose();
socket.close();
JOptionPane.showMessageDialog(frame, "已取消接收,鏈接關閉!", "提示:", JOptionPane.INFORMATION_MESSAGE);
label.setText(" 取消接收,鏈接關閉");
} catch (IOException e1) {
}
}
}
}
}
複製代碼
客戶端代碼:架構
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.Socket;
public class FileTransferClient extends Socket {
private static final String SERVER_IP = "127.0.0.1"; // 服務端IP
private static final int SERVER_PORT = 8899; // 服務端端口
private Socket client;
private DataOutputStream dos;
private DataInputStream dis;
private RandomAccessFile rad;
public FileTransferClient() throws Exception {
super(SERVER_IP, SERVER_PORT);
this.client = this;
//System.out.println("客戶端:成功鏈接服務端");
}
public void sendFile(String filePath, String targetPath) throws Exception {
try {
File file = new File(filePath);
if(file.exists()) {
dos = new DataOutputStream(client.getOutputStream()); //發送信息 getOutputStream方法會返回一個java.io.OutputStream對象
dis = new DataInputStream(client.getInputStream()); //接收遠程對象發送來的信息 getInputStream方法會返回一個java.io.InputStream對象
dos.writeUTF(targetPath); //發送目標路徑
dos.writeUTF(file.getName()); //發送文件名
//System.out.println("客戶端:發送文件名");
rad = new RandomAccessFile(file.getPath(), "r");
/*
* RandomAccessFile是Java輸入輸出流體系中功能最豐富的文件內容訪問類,既能夠讀取文件內容,也能夠向文件輸出數據。
* 與普通的輸入/輸出流不一樣的是,RandomAccessFile支持跳到文件任意位置讀寫數據,RandomAccessFile對象包含一個記錄指針,用以標識當前讀寫處的位置。
* 當程序建立一個新的RandomAccessFile對象時,該對象的文件記錄指針對於文件頭 r表明讀取
*/
dos.flush(); //做用見下方介紹
dos.writeLong(file.length()); //發送文件長度
//System.out.println("客戶端:發送文件長度");
dos.flush();
long size = dis.readLong(); //讀取當前已發送文件長度
//System.out.println("客戶端:開始傳輸文件 ");
int length = 0;
byte[] bytes = new byte[1024]; //每1kb發送一次
if (size < rad.length()) {
rad.seek(size);
//System.out.println("客戶端:文件定位完成");
//移動文件指針
while((length = rad.read(bytes)) > 0){
dos.write(bytes, 0, length);
dos.flush();
//每1kb清空一次緩衝區
//爲了不每讀入一個字節都寫一次,java的輸流有了緩衝區,讀入數據時會首先將數據讀入緩衝區,等緩衝區滿後或執行flush或close時一次性進行寫入操做
}
}
//System.out.println("客戶端:文件傳輸成功 ");
}
} catch (Exception e) {
e.printStackTrace();
} finally { //關閉資源
if(dos != null)
dos.close();
if(dis != null)
dis.close();
if(rad != null)
rad.close();
client.close();
}
}
class cancelActionListener implements ActionListener{ //關閉按鈕監聽器
public void actionPerformed(ActionEvent e3){
try {
//System.out.println("客戶端:文件傳輸取消");
if(dis != null)
dis.close();
if(dos != null)
dos.close();
if(rad != null)
rad.close();
client.close();
} catch (IOException e1) {
}
}
}
}
複製代碼
傳輸文件是一個耗時操做,若直接實例化客戶端對服務器發送數據會形成UI假死的狀況,直到文件傳輸完成後纔會恢復,因此建議在實例化客戶端時單獨創建一個新線程。dom
測試代碼:socket
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
public class MainFrame extends JFrame{
public MainFrame() {
this.setSize(1280, 768);
getContentPane().setLayout(null);
JButton btnNewButton = new JButton("傳輸文件"); //點擊按鈕進行文件傳輸
btnNewButton.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// TODO 自動生成的方法存根
super.mouseClicked(e);
JFileChooser fileChooser = new JFileChooser(); //fileChooser用來選擇要傳輸的文件
fileChooser.setDialogTitle("選擇要傳輸的文件");
int stFile = fileChooser.showOpenDialog(null);
if(stFile == fileChooser.APPROVE_OPTION){ //選擇了文件
JFileChooser targetPathChooser = new JFileChooser(); //targetPathChooser用來選擇目標路徑
targetPathChooser.setDialogTitle("選擇目標路徑");
targetPathChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); //只能選擇路徑
int stPath = targetPathChooser.showOpenDialog(null);
if(stPath == targetPathChooser.APPROVE_OPTION) { //選擇了路徑
//新建一個線程實例化客戶端
new Thread(new NewClient( fileChooser.getSelectedFile().getPath(), targetPathChooser.getSelectedFile().getPath())).start();
}
}
}
});
btnNewButton.setBounds(526, 264, 237, 126);
getContentPane().add(btnNewButton);
}
class NewClient implements Runnable { //用於實例化客戶端的線程
private String fileP; //需複製文件路徑
private String targetP; //目標路徑
public NewClient(String fileP, String targetP) { //構造函數
this.fileP = fileP;
this.targetP = targetP;
}
@Override
public void run() {
// TODO 自動生成的方法存根
try {
@SuppressWarnings("resource")
FileTransferClient ftc = new FileTransferClient();
//實例化客戶端
ftc.sendFile(fileP, targetP);
} catch (Exception e1) {
// TODO 自動生成的 catch 塊
e1.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO 自動生成的方法存根
MainFrame mainFrame = new MainFrame();
mainFrame.setVisible(true);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
@SuppressWarnings("resource")
FileTransferServer server = new FileTransferServer(); // 啓動服務端
server.load();
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
演示:ide
1運行MainFame函數
2點擊傳輸文件測試
3選擇要傳輸的文件this
4選擇目標路徑
5點擊打開
點擊取消
以後重複2 - 5的操做。
你會發現斷點續傳已經實現了