通过Socket连接一次传输多个文件
近期在做一个通过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 写入的字串长度存储在開始的两个字节中。