前面介紹了怎樣經過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開發筆記(序)章節目錄》測試