近期在做一個通過WIFI在手機之間傳輸文件的功能。須要在手機之間建立一個持久的Socket
連接並利用該連接數據傳輸。能夠一次傳輸一個或多個文件。
在一次傳輸多個文件時,遇到了一個困難:怎樣在接收文件時確定文件之間的邊界。
為了在接收端正確的拆分文件,在傳輸文件時須要傳輸每一個文件的大小。
我採用了這樣一種策略:首先發送每一個文件的名稱和大小。然后傳輸文件的內容。
protected void sendFile(Socket socket, File[] files) { long totalSize = 0; byte buf[] = new byte[8192]; int len; try { if (socket.isOutputShutdown()) { return; } DataOutputStream dout = new DataOutputStream( socket.getOutputStream()); dout.writeInt(files.length); for (int i = 0; i < files.length; i++) { dout.writeUTF(files[i].getName()); dout.flush(); dout.writeLong(files[i].length()); dout.flush(); totalSize += files[i].length(); } dout.writeLong(totalSize); for (int i = 0; i < files.length; i++) { BufferedInputStream din = new BufferedInputStream( new FileInputStream(files[i])); while ((len = din.read(buf)) != -1) { dout.write(buf, 0, len); } } System.out.println("文件傳輸完畢"); } catch (Exception e) { e.printStackTrace(); Log.d(TAG,"send file exception"); } return; }
接收文件時有些復雜。每次從輸入流中讀入緩存中的數據有可能包括多個文件的內容,
須要利用每一個文件的大小信息把緩存中的數據放入不同的文件。
protected void receiveFile(Socket socket) { File dirs = new File(mFilePath); if (!dirs.exists()) { dirs.mkdirs(); } DataInputStream din = null; int fileNum = 0; long totalSize = 0; FileInfo[] fileinfos = null; try { din = new DataInputStream(new BufferedInputStream( socket.getInputStream())); fileNum = din.readInt(); fileinfos = new FileInfo[fileNum]; for (int i = 0; i < fileNum; i++) { fileinfos[i] = new FileInfo(); fileinfos[i].mFileName = din.readUTF(); fileinfos[i].mFileSize = din.readLong(); } totalSize = din.readLong(); } catch (IOException e) { e.printStackTrace(); Log.d(TAG,"readInt Exception"); System.exit(0); } System.out.println(fileNum); System.out.println(totalSize); for (FileInfo fileinfo : fileinfos) { System.out.println(fileinfo.mFileName); System.out.println(fileinfo.mFileSize); } // // ///////////////////////////////////////////////////////////////// int leftLen = 0; // 寫滿文件后緩存區中剩余的字節長度。int bufferedLen = 0; // 當前緩沖區中的字節數 int writeLen = 0; // 每次向文件里寫入的字節數 long writeLens = 0; // 當前已經向單個文件里寫入的字節總數 long totalWriteLens = 0; // 寫入的所有字節數 byte buf[] = new byte[8192]; for (int i = 0; i < fileNum; i++) { writeLens = 0; try { FileOutputStream fout = new FileOutputStream(mFilePath + fileinfos[i].mFileName); while (true) { if (leftLen > 0) { bufferedLen = leftLen; } else { bufferedLen = din.read(buf); } if (bufferedLen == -1) return; System.out.println("readlen" + bufferedLen); // 假設已寫入文件的字節數加上緩存區中的字節數已大於文件的大小,僅僅寫入緩存區的部分內容。 if (writeLens + bufferedLen >= fileinfos[i].mFileSize) { leftLen = (int) (writeLens + bufferedLen - fileinfos[i].mFileSize); writeLen = bufferedLen - leftLen; fout.write(buf, 0, writeLen); // 寫入部分 totalWriteLens += writeLen; move(buf, writeLen, leftLen); break; } else { fout.write(buf, 0, bufferedLen); // 所有寫入 writeLens += bufferedLen; totalWriteLens += bufferedLen; if (totalWriteLens >= totalSize) { //mListener.report(GroupChatActivity.FAIL, null); return; } leftLen = 0; } //mListener.report(GroupChatActivity.PROGRESS, //(int) (totalWriteLens * 100 / totalSize)); } // end while fout.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); Log.d(TAG,"receive file Exception"); } } // end for //mListener.report(GroupChatActivity.FAIL, null); }
注:在傳輸文件時還傳輸了文件的總大小,這樣為了在接收文件時判定接收是否結束。
另一種傳輸方法比較復雜但更加靈活發送文件時依次傳輸每一個文件的名稱。大小和內容。
相比上一個方法這樣的發送方式接受時更難處理。
由於每次從輸入流中讀入緩存的數據可能包括了上一個文件的內容。下一個文件的名稱和大小。
因為數據已被讀入了緩存,這就不能利用DataInputStream的方法讀取UTF字符串和Int,
必須從緩存中解析。
介紹兩種解析方法
利用ByteArrayInputStream 把緩存中的內容轉化為內存流然后利用DataInputStream讀取。
手動解析,利用位運算拼接出Int。
注:Int 的長度為4是確定的。
WriteUTF 寫入的字串長度存儲在開始的兩個字節中。