記錄一次用TCP協議傳檔案的探索
總的思路,客戶端創建Socket物件和服務端通信,通過Socket物件獲取的IO流進行資料傳輸.基本的代碼如下:
客戶端(發送端)部分
public class Client {
public static void main(String[] args) throws IOException {
// 要上傳的檔案
File file = new File("C:\\Users\\Yaoxi\\Pictures\\Saved Pictures\\影像.jpg");
// 創建一個socket物件用來連接服務器
// Socket(套接字)是兩臺計算機之間進行通信的端點
Socket socket = new Socket("127.0.0.1", 7341);
// 通過socket物件獲取輸入輸出流
// 通過流分別創建字符緩沖輸入流(讀服務器反饋)和列印流(傳資料給服務器)
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream ps = new PrintStream(socket.getOutputStream());
// 發檔案資料
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = fis.read(bytes)) != -1) {
ps.write(bytes, 0, len);
}
// 讀服務器反饋
String s = br.readLine();
System.out.println(s);
// 資源釋放
socket.close();
}
}
服務器(接收端)部分
public class Server {
public static void main(String[] args) throws IOException {
// 創建ServerSocket物件
// ServerSocket會等待請求通過網路進入
ServerSocket ss = new ServerSocket(7341);
// 監聽客戶端的連接(阻塞方法,如果沒有會一直等待)
Socket socket = ss.accept();
// 獲取I/O流
PrintStream ps = new PrintStream(socket.getOutputStream());
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
// 設定檔案接收位置
File fileName = new File("D:\\server\\a.jpg");
// 收檔案
FileOutputStream fos = new FileOutputStream(fileName);
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = br.read()) != -1) {
fos.write(bytes, 0, len);
}
// 發反饋給客戶端
ps.println("接收完畢");
}
}
這樣子將會存在一定的問題
1.客戶端未發送結束標記,服務端的read會一直等待讀取
2.檔案名被固定寫死,每次接收都會覆寫
3.服務端一次運行后只能接收一次檔案
4.服務端不能同時處理多個檔案上傳
對于問題1,可以給客戶端發檔案的時候加個結束標記
shutdownOutput()方法會通過關閉io流作為檔案傳輸結束的標記通知服務端.
客戶端(發送端)部分改造后
public class Client {
public static void main(String[] args) throws IOException {
// 要上傳的檔案
File file = new File("C:\\Users\\Yaoxi\\Pictures\\Saved Pictures\\影像.jpg");
// 創建一個socket物件用來連接服務器
// Socket(套接字)是兩臺計算機之間進行通信的端點
Socket socket = new Socket("127.0.0.1", 7341);
// 通過socket物件獲取輸入輸出流
// 通過流分別創建字符緩沖輸入流(讀服務器反饋)和列印流(傳資料給服務器)
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream ps = new PrintStream(socket.getOutputStream());
// 發檔案資料
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = fis.read(bytes)) != -1) {
ps.write(bytes, 0, len);
}
// 寫結束標記
socket.shutdownOutput();
// 讀服務器反饋
String s = br.readLine();
System.out.println(s);
// 資源釋放
socket.close();
}
}
對于問題2,可以采取加一個隨機的檔案名去解決.
在客戶端發送的時候可以先發一次檔案名,然后服務端收到后拼接隨機的字串,作為即將接收的檔案名,再接收檔案存入本地磁盤中.
這個程序中會遇到另一個問題,接收檔案名的時候服務端用的是BufferedReader,有緩沖區的存在導致之后傳輸檔案時,可能會出現資料丟失.因此當檔案名接收到后,服務端回寫一條資訊,通知客戶端再發檔案,作為操作的分隔.
客戶端(發送端)部分改造后
public class Client {
public static void main(String[] args) throws IOException {
// 要上傳的檔案
File file = new File("C:\\Users\\Yaoxi\\Pictures\\Saved Pictures\\影像.jpg");
// 創建一個socket物件用來連接服務器
// Socket(套接字)是兩臺計算機之間進行通信的端點
Socket socket = new Socket("127.0.0.1", 7341);
// 通過socket物件獲取輸入輸出流
// 通過流分別創建字符緩沖輸入流(讀服務器反饋)和列印流(傳資料給服務器)
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream ps = new PrintStream(socket.getOutputStream());
// 給服務器傳遞檔案名
ps.println(file.getName());
// 接收服務器回寫的分隔資訊
String start = br.readLine();
System.out.println(start);
// 再發檔案資料
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = fis.read(bytes)) != -1) {
ps.write(bytes, 0, len);
}
// 寫結束標記
socket.shutdownOutput();
// 讀服務器反饋
String s = br.readLine();
System.out.println(s);
// 資源釋放
socket.close();
}
}
服務器(接收端)部分改造后
public class Server {
public static void main(String[] args) throws IOException {
// 創建ServerSocket物件
// ServerSocket會等待請求通過網路進入
ServerSocket ss = new ServerSocket(7341);
// 監聽客戶端的連接(阻塞方法,如果沒有會一直等待)
Socket socket = ss.accept();
// 獲取I/O流
PrintStream ps = new PrintStream(socket.getOutputStream());
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
// 讀檔案名
String fileName = br.readLine();
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println(socket.getInetAddress().getHostAddress() + " 發送檔案 " + fileName);
// 回寫訊息給客戶端
// 作為兩個讀取操作的分隔,防止用InputStream讀資料時,BufferedReader緩沖區內容丟失
ps.println("開始接收檔案");
// 創建流關聯到本地硬碟
File dir = new File("D:\\server");
// 防止檔案重復使用UUID作為隨機字串
String uuid = UUID.randomUUID().toString().replace("-", "");
// 設定檔案接收位置
File dir = new File("D:\\server");
// 收檔案并拼接檔案名
FileOutputStream fos = new FileOutputStream(dir + "\\" + uuid + "_" + fileName);
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = br.read()) != -1) {
fos.write(bytes, 0, len);
}
// 發反饋給客戶端
ps.println("接收完畢");
}
}
對于問題3,在服務端的處理方法外加一層回圈即可
服務器(接收端)部分改造后
public class Server {
public static void main(String[] args) throws IOException {
// 創建ServerSocket物件
// ServerSocket會等待請求通過網路進入
ServerSocket ss = new ServerSocket(7341);
// 使用死回圈保證程式一直能接收請求
while (true) {
// 監聽客戶端的連接(阻塞方法,如果沒有會一直等待)
Socket socket = ss.accept();
// 獲取I/O流
PrintStream ps = new PrintStream(socket.getOutputStream());
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
// 讀檔案名
String fileName = br.readLine();
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println(socket.getInetAddress().getHostAddress() + " 發送檔案 " + fileName);
// 回寫訊息給客戶端
// 作為兩個讀取操作的分隔,防止用InputStream讀資料時,BufferedReader緩沖區內容丟失
ps.println("開始接收檔案");
// 創建流關聯到本地硬碟
File dir = new File("D:\\server");
// 防止檔案重復使用UUID作為隨機字串
String uuid = UUID.randomUUID().toString().replace("-", "");
// 設定檔案接收位置
File dir = new File("D:\\server");
// 收檔案并拼接檔案名
FileOutputStream fos = new FileOutputStream(dir + "\\" + uuid + "_" + fileName);
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = br.read()) != -1) {
fos.write(bytes, 0, len);
}
// 發反饋給客戶端
ps.println("接收完畢");
}
}
}
對于問題4,在服務端改用多執行緒去實作
其中需要注意 accept方法是個阻塞方法,所以它不能放在執行緒的run方法中.否則就會一直開執行緒,然后每個執行緒在執行accept的時候阻塞.
服務器(接收端)部分改造后
public class Server {
public static void main(String[] args) throws IOException {
// 創建ServerSocket物件
// ServerSocket會等待請求通過網路進入
ServerSocket ss = new ServerSocket(7341);
// 使用死回圈保證程式一直能接收請求
// 可以多次訪問
while (true) {
// 監聽客戶端的連接(阻塞方法)
// (用來阻塞main執行緒,當有訪問的時候才繼續運行開執行緒)
Socket socket = ss.accept();
// 用Runnable開執行緒
Runnable runnable = new Runnable() {
@Override
public void run() {
FileOutputStream fos = null;
try {
// 獲取I/O流
PrintStream ps = new PrintStream(socket.getOutputStream());
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
// 讀檔案名
String fileName = br.readLine();
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println(socket.getInetAddress().getHostAddress() + " 發送檔案 " + fileName);
// 回寫訊息給客戶端
// 作為兩個讀取操作的分隔,防止用InputStream讀資料時,BufferedReader緩沖區內容丟失)
ps.println("開始接收檔案");
// 創建流關聯到本地硬碟
File dir = new File("D:\\server");
// 防止檔案重復
String uuid = UUID.randomUUID().toString().replace("-", "");
fos = new FileOutputStream(dir + "\\" + uuid + "_" + fileName);
byte[] bytes = new byte[1024 * 8];
int len;
// 從客戶端的流讀
while ((len = in.read(bytes)) != -1) {
// 寫到本地檔案的流
fos.write(bytes, 0, len);
}
// 回寫給客戶端
ps.println("接收完畢");
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println(socket.getInetAddress().getHostAddress() + " 發送檔案接收完畢");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 釋放資源
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};
// 提交到執行緒池
new Thread(runnable).start();
}
}
}
現在有了一個執行緒,不如順便加個執行緒池,方便使用.
服務器(接收端)部分改造后
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(7341);
// 創建執行緒池
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5,
3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 可以多次訪問
while (true) {
// 監聽客戶端的連接(阻塞方法)
// (用來阻塞main執行緒,當有訪問的時候才繼續運行開執行緒)
Socket socket = ss.accept();
// 用Runnable開執行緒
Runnable runnable = new Runnable() {
@Override
public void run() {
FileOutputStream fos = null;
try {
// 獲取I/O流
PrintStream ps = new PrintStream(socket.getOutputStream());
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
// 讀檔案名
String fileName = br.readLine();
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println(socket.getInetAddress().getHostAddress() + " 發送檔案 " + fileName);
// 回寫訊息給客戶端
// (作為兩個讀取操作的分隔,
// 防止用InputStream讀資料時,
// BufferedReader緩沖區內容丟失)
ps.println("開始接收檔案");
// 創建流關聯到本地硬碟
File dir = new File("D:\\server");
// 防止檔案重復
String uuid = UUID.randomUUID().toString().replace("-", "");
fos = new FileOutputStream(dir + "\\" + uuid + "_" + fileName);
byte[] bytes = new byte[1024 * 8];
int len;
// 從客戶端的流讀
while ((len = in.read(bytes)) != -1) {
// 寫到本地檔案的流
fos.write(bytes, 0, len);
}
// 回寫給客戶端
ps.println("接收完畢");
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println(socket.getInetAddress().getHostAddress() + " 發送檔案接收完畢");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 釋放資源
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};
// 提交到執行緒池
pool.submit(runnable);
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/199881.html
標籤:其他
