首页 > 代码库 > 通过Socket连接一次传输多个文件

通过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 写入的字串长度存储在开始的两个字节中。