Java開發筆記(一百一十五)使用Socket開展文件傳輸

前面介紹了怎樣經過Socket在客戶端與服務端之間傳輸文本,固然Socket也支持在客戶端與服務端之間傳輸文件,由於文件自己就是經過I/O流實現讀寫操做的,因此在套接字的輸入輸出流中傳輸文件真是再合適不過了。只是套接字屬於長鏈接,假若Socket一直不關閉,鏈接將老是處於就緒狀態,也就沒法判斷文件數據是否已經傳輸完成。爲了檢驗文件傳輸的結束時刻,能夠考慮實時下列的兩種技術方案之一:
一、客戶端每次連上Socket以後,只發送一個文件的數據,且發送完畢的同時當即關閉套接字,從而告知服務端已經成功發送文件,沒必要繼續保留這個Socket。
二、客戶端的Socket連上了服務端,仍然像文本傳輸那樣保持長鏈接,可是另外定義文件傳輸的專用數據格式,好比每次傳輸操做都由開始指令、文件數據、結束指令這些要素組成。而後客戶端按照該格式發送文件,服務端也按照該格式接收文件,因爲傳輸操做包含開始指令和結束指令,因此即便客戶端不斷開鏈接,服務端也能憑藉開始指令和結束指令來分清文件數組的開頭和結尾。
考慮到編碼的複雜度,這裏採起前一種方案,即每次Socket鏈接只發送一個文件。據此編寫的文件發送任務框架相似於文本發送任務,差異在於待發送的數據來自於本地文件,詳細的客戶端主要代碼示例以下:html

//定義一個文件發送任務
public class SendFile implements Runnable {
	// 如下爲Socket服務器的IP和端口,根據實際狀況修改
	private static final String SOCKET_IP = "192.168.1.8";
	private static final int FILE_PORT = 52000; // 文件傳輸專用端口
	private String mFilePath; // 待發送的文件路徑
	
	public SendFile(String filePath) {
		mFilePath = filePath;
	}

	@Override
	public void run() {
		PrintUtils.print("向服務器發送文件:" + mFilePath);
		// 建立一個套接字對象。同時根據指定路徑構建文件輸入流對象
		try (Socket socket = new Socket();
				FileInputStream fis = new FileInputStream(mFilePath)) {
			// 命令套接字鏈接指定地址的指定端口,超時時間爲3秒
			socket.connect(new InetSocketAddress(SOCKET_IP, FILE_PORT), 3000);
			// 獲取套接字對象的輸出流
			OutputStream writer = socket.getOutputStream();
			long totalLength = fis.available(); // 文件的總長度
			int tempLength = 0; // 每次發送的數據長度
			double sendedLength = 0; // 已發送的數據長度
			byte[] data = new byte[1024 * 8]; // 每次發送數據的字節數組
			// 如下從文件中循環讀取數據
			while ((tempLength = fis.read(data, 0, data.length)) > 0) {
				writer.write(data, 0, tempLength); // 往Socket鏈接中寫入數據
				sendedLength += tempLength; // 累加已發送的數據長度
				// 計算已發送數據的百分比,並打印當前的傳輸進度
				String ratio = "" + (sendedLength / totalLength * 100);
				PrintUtils.print("已傳輸:" + ratio.substring(0, 4) + "%");
			}
			PrintUtils.print(mFilePath+" 文件發送完畢");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

至於服務端的文件接收任務,依然爲每一個連上的客戶端分配子線程,並把接收到的數據保存爲文件形式,詳細的服務端主要代碼示例以下:數組

//定義一個文件接收任務
public class ReceiveFile implements Runnable {
	private static final int FILE_PORT = 52000; // 文件傳輸專用端口

	@Override
	public void run() {
		PrintUtils.print("接收文件的Socket服務已啓動");
		try {
			// 建立一個服務端套接字,用於監聽客戶端Socket的鏈接請求
			ServerSocket server = new ServerSocket(FILE_PORT);
			while (true) { // 持續偵聽客戶端的鏈接
				// 收到了某個客戶端的Socket鏈接請求,並得到該客戶端的套接字對象
				Socket socket = server.accept();
				// 啓動一個服務線程負責與該客戶端的交互操做
				new Thread(new ServerTask(socket)).start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 定義一個伺候任務,好生招待這位顧客
	private class ServerTask implements Runnable {
		private Socket mSocket; // 聲明一個套接字對象
		
		public ServerTask(Socket socket) throws IOException {
			mSocket = socket;
		}

		@Override
		public void run() {
			PrintUtils.print("開始接收文件");
			int random = new Random().nextInt(1000); // 生成隨機數
			String file_path = "D:/" + random + ".jpg"; // 本地臨時保存的文件
			// 根據指定的臨時路徑構建文件輸出流對象
			try (FileOutputStream fos = new FileOutputStream(file_path)) {
				// 獲取套接字對象的輸入流
				InputStream reader = mSocket.getInputStream();
				int tempLength = 0; // 每次接收的數據長度
				byte[] data = new byte[1024 * 8]; // 每次接收數據的字節數組
				// 如下從Socket鏈接中循環接收數據
				while ((tempLength = reader.read(data, 0, data.length)) > 0) {
					fos.write(data, 0, tempLength); // 把接收到的數據寫入文件
				}
				// 注意客戶端的Socket要先調用close方法,服務端纔會退出上面的循環
				mSocket.close(); // 關閉套接字鏈接
				PrintUtils.print(file_path+" 文件接收完畢");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

接着服務端程序開啓Socket專用的文件接收線程,線程啓動代碼以下所示:服務器

		// 啓動一個文件接收線程
		new Thread(new ReceiveFile()).start();

而後客戶端程序啓動多個文件發送任務,而且每一個任務都使用單獨的分線程來執行,因而文件發送代碼以下所示:框架

	// 發送本地文件
	private static void testSendFile() {
		// 爲文件發送任務開啓分線程
		new Thread(new SendFile("E:/bliss.jpg")).start();
		// 爲文件發送任務開啓分線程
		new Thread(new SendFile("E:/qq_qrcode.png")).start();
	}

 

最後完整走一遍流程,先運行服務端的測試程序,再運行客戶端的測試程序,觀察到的客戶端日誌以下:dom

12:42:08.258 Thread-1 向服務器發送文件:E:/qq_qrcode.png
12:42:08.258 Thread-0 向服務器發送文件:E:/bliss.jpg
12:42:08.351 Thread-1 E:/qq_qrcode.png已傳輸:47.6%
12:42:08.352 Thread-1 E:/qq_qrcode.png已傳輸:95.2%
12:42:08.354 Thread-0 E:/bliss.jpg已傳輸:0.41%
12:42:08.355 Thread-0 E:/bliss.jpg已傳輸:0.83%
12:42:08.356 Thread-0 E:/bliss.jpg已傳輸:1.25%
12:42:08.357 Thread-0 E:/bliss.jpg已傳輸:1.67%
12:42:08.354 Thread-1 E:/qq_qrcode.png已傳輸:100.%
12:42:08.358 Thread-1 E:/qq_qrcode.png 文件發送完畢
12:42:08.365 Thread-0 E:/bliss.jpg已傳輸:2.09%
12:42:08.366 Thread-0 E:/bliss.jpg已傳輸:2.50%
…………這裏省略中間的傳輸進度…………
12:42:08.461 Thread-0 E:/bliss.jpg已傳輸:99.9%
12:42:08.462 Thread-0 E:/bliss.jpg已傳輸:100.%
12:42:08.462 Thread-0 E:/bliss.jpg 文件發送完畢

同時觀察到下面的服務端日誌:socket

12:41:56.718 Thread-0 接收文件的Socket服務已啓動
12:42:08.295 Thread-1 開始接收文件
12:42:08.305 Thread-2 開始接收文件
12:42:08.362 Thread-2 D:/265.jpg 文件接收完畢
12:42:08.462 Thread-1 D:/34.jpg 文件接收完畢

根據以上的客戶端日誌以及服務端日誌,可知經過Socket成功實現了文件傳輸功能。ide



更多Java技術文章參見《Java開發筆記(序)章節目錄測試

相關文章
相關標籤/搜索