JAVA NIO服务器间连续发送文件(本地测试版)

2023-05-06,,

说在前面:给我的需求是实现从服务器A将大量文件(大概几十TB)发送到服务器B,在A服务器生成文件的MD5码,并在服务器B进行md5验证,验证通过保存。

我的实现思路:

  • 将待上传的所有文件目录生成一个txt文件,格式如下。前缀中,当后面的数字等于9999的时候,前面的数字会自行相加。(此处加前缀的目的是为了整齐,而且失败了便于查询。)

AAA0000:D:\upload\addChannel.html

AAA0001:D:\upload\addChannel2.html

AAA0002:D:\upload\addContactPerson.html

AAA0003:D:\upload\admin.html

AAA0004:D:\upload\businessOfChannel.html

....

AAA9999:D:\upload\admin1.html

AAB0000:D:\upload\businessOfChannel1.html

...

  • 每次读取一条目录,进行上传。

  • 本地测试版未去实现的部分,没有把成功和失败的目录写到文件中,也没有添加日志。

2017/07/07增加部分

  • 添加部分注释

  • 添加对于大文件上传的解决方案

2017/07/12增加部分

  • 经过对代码的研究以及测试表明,2017/07/07修改的大文件上传有问题,问题出在bytebuffer的缓存有限制,byte.allocate(int)中的参数类型为int,即最大缓存为Integer.MAX_VALUE,即2^31-1的字节,大概是2G的大小。

  • 如果确定文件不会大于2G的话,用2017/07/07的部分应该足够,但是如果可以的话,依然推荐2017/07/12新增的部分代码

  • 新增的部分,不再原基础上进行修改,而是新增代码,只需要在客户端和服务器调用方法的时候,将handler.sendData(socketChannel, path, pathPre)改成handler.sendData2(socketChannel, path, pathPre),将this.handler.excute((ServerSocketChannel) s.channel())改成this.handler.excute2((ServerSocketChannel) s.channel())即可,当然在handler类中需要新增

    相应的方法(excute2(),receiveData2(),sendData2())

  • 另外添加RequestObject类,用于替代execute方法中的RequestObject2类,在execute方法中使用。

  • 在服务器端,原来是将接受到的文件先存到字节数组,然后对字节数组进行md5验证,但是字节数组的大小依然受到Integer.MAX_VALUE的限制。所以现在改成,直接将接收到的字节数组写入到文件,然后直接对文件进行MD5验证。这样excute2的方法会比之前逻辑有些变化。

  • 代码依然不够整洁,需要后续继续修改。

第一部分:将文件目录存储到文本中,文件夹不进行存储。

import java.io.File;
import java.io.FileOutputStream;

public class ReadAllPaths {
	private static final String rootPath="D:/upload/"; //the root path of the files which will be copied 
	private static final String filePath="G:/temp/unUploadedFilePath.txt";//the record of all files path
	/*
	 * the items of prefix and num construct the path prefix,for example AAA0001
	 * and it's mainly  convenient for searching
	 */
	private String prefix="AAA"; 
	private int num=0;
	/**
	 * main
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		ReadAllPaths  paths=new ReadAllPaths();
		File file=new File(filePath);
		if(file.exists()){
			file.delete();
		}
		FileOutputStream out=new FileOutputStream(file,true);
		paths.getAllPaths(rootPath, out);
		out.close();
	}
	
	/**
	 * get all path out
	 * @param root
	 * @param out
	 * @throws Exception
	 */
	private  void getAllPaths(String root,FileOutputStream out) throws Exception{
		File file=new File(root);
		
		if(file.isDirectory()){
			try{if(file.list().length==0){
				return;
			}else{
				String[] files=file.list();
				for(String f:files){
					getAllPaths(root+f+File.separator, out);
				}
			}
			}catch(NullPointerException npe){
				return;
			}
		}else{
			String pathNum=getPathNum();
			String path=file.getAbsolutePath();
			out.write((pathNum+":"+path+"\n").getBytes());
		}
	}
	/**
	 * get the path prefix
	 * @return
	 */
	private String getPathNum(){
		StringBuilder sb=new StringBuilder();
		sb.append(getPrefix()).append(getNum());
		setNum();
		return sb.toString();
	}
	
	/**
	 * get the String prefix of path prefix
	 * @return
	 */
	private String getPrefix() {
		return prefix;
	}
	/**
	 * set the String prefix of path prefix
	 * for example:AAA AAB AAC....AAZ ABA....AZZ BAA...
	 */
	private void setPrefix() {
		char[] ch=new char[3];
		ch=getPrefix().toCharArray();
		if(ch[2]!='Z'){
			ch[2]++;
		}else{
			ch[2]='A';
			if(ch[1]!='Z'){
				ch[1]++;
			}else{
				ch[1]='A';
				ch[0]++;
			}
		}
		prefix=new String(ch);
	}
	/**
	 * get the int prefix of path prefix
	 * @return
	 */
	private String getNum() {
		StringBuffer sb=new StringBuffer();
		if(num<10){
			sb.append("000").append(num);
		}else if(num<100){
			sb.append("00").append(num);
		}else if(num<1000){
			sb.append("0").append(num);
		}else{
			sb.append(num);
		}
		return sb.toString();
	}
	/**
	 * set the int prefix of path prefix
	 * and the max num is 9999 and the min is 0000
	 */
	private void setNum() {
		if(num<9999){			
			num++;
		}else{
			num=0;
			setPrefix();
		}
	}
}

第二部分,服务器端代码

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;

public class Server {
	Selector selector = null;
	ServerSocketChannel serverSocketChannel = null;
	private NioserverHandler2 handler;

	public Server() throws IOException {
		selector = Selector.open();
		// 打开服务器套接字通道
		serverSocketChannel = ServerSocketChannel.open();

		// 调整通道的阻塞模式非阻塞
		serverSocketChannel.configureBlocking(false);
		//serverSocketChannel.socket().setReuseAddress(true);这里没明白有什么用,//所以注释了
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));

		    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
	}

	public Server(NioserverHandler2 handler) throws IOException {

		this();

		this.handler = handler;
		while (selector.select() > 0) {
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			while (it.hasNext()) {
				SelectionKey s = it.next();
				it.remove();
				this.handler.excute((ServerSocketChannel) s.channel());
			}
		}
	}

	public static void main(String[] args) throws IOException {
		new Server(new NioserverHandler2());
	}
}
public class NioserverHandler2 {

        //2017/07/07增加注释
        /*该目录用于服务端文件的存储。
        */
	private final static String DIRECTORY = "G:\\NioRequest\\";
	/*
	*2017/07/07增加
	*用于存储上传文件成功失败情况的目录,
	*成功的放在succeed.log中
	*格式为客户端传过来的带前缀的路径,无特殊格式,每个一行
	*失败的放在failed.log中
	*格式为reason+带前缀路径
	*/
	private final static String ResultPath = "G:\\serverLog\\";
	/*
	*2017/07/07修改
	*新增构造方法,检测succeed.log,failed.log以及其目录是否存在,不存在则创建
	*/
	public NioserverHandler2() {
		File file = new File(ResultPath);
		File file1 = new File(ResultPath + "succeed.log");
		File file2 = new File(ResultPath + "failed.log");
		if (!file.exists()) {
			file.mkdirs();
		}
		if (!file1.exists()) {
			try {
				file1.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if (!file2.exists()) {
			try {
				file2.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
	        }

	}

	/**
	 * 这里边我们处理接收和发送
	 * 
	 * @param serverSocketChannel
	 */
	public void excute(ServerSocketChannel serverSocketChannel) {
		SocketChannel socketChannel = null;
		/*
		*2017/07/07增加该字段
		*自己测试中发现,有些文件可以读写执行,但是却无法正确发送
		*所以在client发送的fileInfo中新增了一个字段,如果把文件读取到字节数组失败
		*则设置字节数组为空
		*此时字节数组为空有两种情况:
		*文件为0KB大小,还有上面的一种情况,
		*前者需要直接新建一个空文件,后者需要报错
		*所以client端新增一个字段,用于区分这两种情况。
		*第一种情况是fileInfo中设置一个well字符串
		*第二种情况是fileInfo中设置一个error字符串
		*
		*此处新增的status由于区分不同情况
		*status==0 文件传送正确,并且MD5验证通过
		*status==1 文件传送正确,但是MD5验证未通过
		*status==2 文件传输失败
		*以后如果有新的错误,会在这里继续追加状态
		*/
		int status=0;
		try {
			socketChannel = serverSocketChannel.accept(); // 等待客户端连接

			RequestObject2 requestObject = receiveData(socketChannel);// 接数据                        
			//md5验证,用的是apache的,2017/07/07添加注释
			String md5 = DigestUtils.md5Hex(requestObject.getContents());
			//用于对客户端的相应,其实在本程序中用处不太大
			String response = "";
			//2017/07/07添加该字段,用于判断成功失败    
			boolean flag=true;
			/*
			*2017/07/07对于判断进行修改,新增了判断status字段是否为error
			*至于下面的responseObject不用关于,只是为了方便固定格式的字符串而已
			*/
			if(requestObject.getStatus().equals("error")){
				flag=false;
				status=2;
				response = (new ResponseObject("failed", requestObject.getAbsolutePath(), "fail error")).toString();
			}else{
				if (md5.equals(requestObject.getMd5())) {
					response = (new ResponseObject("succeed", requestObject.getAbsolutePath(), "")).toString();
					File file = new File(DIRECTORY + requestObject.getRelativePath());
					if (!file.exists()) {
						file.mkdirs();
					}
					File file1 = new File(DIRECTORY + requestObject.getRelativePath() + requestObject.getFilename());
					if (!file1.exists()) {
						file1.createNewFile();
					}
					FileOutputStream fos = new FileOutputStream(file1);
					fos.write(requestObject.getContents());
					fos.close();
				} else {
					flag=false;
					status=1;
					response = (new ResponseObject("failed", requestObject.getAbsolutePath(), "md5验证失败")).toString();
				}
			}
			//将处理结果写入到succeed.log和failed.log中
			writeRusultToFile(flag, requestObject.getAbsolutePath(),status);
			//响应客户端
			responseData(socketChannel, response);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/*
	*2017/07/12新增代码
	*md5验证方式从验证字节数组改成验证文件流。
	*将之前写入文件的部分代码删除,因为在receiveData2已经将文件进行了写入。
	*
	*/
	public void excute2(ServerSocketChannel serverSocketChannel) {
		SocketChannel socketChannel = null;
		int status=0;
		try {
			socketChannel = serverSocketChannel.accept(); // 等待客户端连接

			RequestObject requestObject = receiveData2(socketChannel);// 接数据
			String md5 = DigestUtils.md5Hex(new FileInputStream(new File(DIRECTORY+requestObject.getRelativePath()+requestObject.getFilename())));
			System.out.println(md5);
			String response = "";
			boolean flag = true;
			if(requestObject.getStatus().equals("error")){
				flag=false;
				status=2;
				response = (new ResponseObject("failed", requestObject.getAbsolutePath(), "fail error")).toString();
			}else{
				if (md5.equals(requestObject.getMd5())) {
					response = (new ResponseObject("succeed", requestObject.getAbsolutePath(), "")).toString();
				} else {
					flag=false;
					status=1;
					response = (new ResponseObject("failed", requestObject.getAbsolutePath(), "md5验证失败")).toString();
				}
			}

			writeRusultToFile(flag, requestObject.getAbsolutePath(),status);
			System.out.println(response);

			responseData(socketChannel, response);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * <p>
	 * 读取通道中的数据到Object里去
	 * </p>
	 * 
	 * @param socketChannel
	 * @return
	 * @throws IOException
	 */
	public RequestObject2 receiveData(SocketChannel socketChannel) throws IOException { 

		// 文件名
		String fileName = null;
		String relativePath = null;
		String absolutePath = null;
		String md5 = null;
		//2017/07/07新增字段,因为RequestObject2字段进行了修改,所以添加
		String status=null;
		// 文件长度
		int contentLength = 0;
		// 文件内容
		byte[] contents = null;
		// 由于我们解析时前4个字节是文件名长度
		int capacity = 4;
		ByteBuffer buf = ByteBuffer.allocate(capacity);
		int size = 0;
		byte[] bytes = null;
		// 拿到文件名的长度
		size = socketChannel.read(buf);
		if (size >= 0) {
			buf.flip();
			capacity = buf.getInt();
			buf.clear();
		}

		buf = ByteBuffer.allocate(capacity);
		size = socketChannel.read(buf);
		if (size >= 0) {
			buf.flip();
			bytes = new byte[size];
			buf.get(bytes);
			buf.clear();
		}
		String fileInfo = new String(bytes);
		System.out.println(fileInfo);
		/*2017/07/07增加注释,并添加字段
		*并进行部分修改
		*/
		status=fileInfo.split(";")[0];
		fileName = fileInfo.split(";")[1];
		relativePath = fileInfo.split(";")[2];
		absolutePath = fileInfo.split(";")[3];
		md5 = fileInfo.split(";")[4];
		// 拿到文件长度
		capacity = 4;
		buf = ByteBuffer.allocate(capacity);
		size = socketChannel.read(buf);
		if (size >= 0) {
			buf.flip();
			// 文件长度是可要可不要的,如果你要做校验可以留下
			capacity = buf.getInt();
			buf.clear();
		}
		if (capacity == 0) {
			contents = new byte[] {};
		} else {
			// 用于接收buffer中的字节数组
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			// 文件可能会很大
			// capacity = 1024;
			buf = ByteBuffer.allocate(capacity);
			while ((size = socketChannel.read(buf)) >= 0) {
				buf.flip();
				bytes = new byte[size];
				buf.get(bytes);
				baos.write(bytes);
				buf.clear();
			}
			contents = baos.toByteArray();
		}

		RequestObject2 requestObject = new RequestObject2(fileName, relativePath, absolutePath, md5, contents,status);

		return requestObject;
	}


	private void responseData(SocketChannel socketChannel, String response) {
		ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
		try {
			socketChannel.write(buffer);
			buffer.clear();
			// 确认要发送的东西发送完了关闭output 不然它端接收时socketChannel.read(Buffer)
			// 很可能造成阻塞 ,可以把这个(L)注释掉,会发现客户端一直等待接收数据
			socketChannel.socket().shutdownOutput();// (L)
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/*
	*2017/07/12新增方法,用于excute2()方法
	*大部分代码和2017/07/07的代码相似,因为客户端发送的文件大小可能超出int类型,所以改成long类型,所以服务端也自然进行了修改
	*因为可能文件过大,所以直接将接收到的字节数组写入到文件中。
	*这里的注释可能比较少
	*/
	public RequestObject receiveData2(SocketChannel socketChannel) throws IOException {
		String status=null;
		String fileName = null;
		String relativePath = null;
		String absolutePath = null;
		String md5 = null;
		int contentLength = 0;
		// 由于我们解析时前4个字节是文件名长度
		int capacity = 4;
		ByteBuffer buf = ByteBuffer.allocate(capacity);
		int size = 0;
		byte[] bytes = null;
		// 拿到文件名的长度
		size = socketChannel.read(buf);
		if (size >= 0) {
			buf.flip();
			capacity = buf.getInt();
			buf.clear();
		}
		buf = ByteBuffer.allocate(capacity);
		size = socketChannel.read(buf);
		if (size >= 0) {
			buf.flip();
			bytes = new byte[size];
			buf.get(bytes);
			buf.clear();
		}
		
		status = fileInfo.split(";")[0];
		fileName = fileInfo.split(";")[1];
		relativePath = fileInfo.split(";")[2];
		absolutePath = fileInfo.split(";")[3];
		md5 = fileInfo.split(";")[4];
		// 拿到文件长度,因为long类型占8个字节,所以这里为8
		capacity = 8;
		buf = ByteBuffer.allocate(capacity);
		size = socketChannel.read(buf);
		long cap=0;
		if (size >= 0) {
			buf.flip();
			// 文件长度显得不那么重要了
			cap = buf.getLong();
			buf.clear();
		}
		System.out.println(cap);
		if (status.equals("well")) {
			File file = new File(DIRECTORY + relativePath);
			if (!file.exists()) {
				file.mkdirs();
			}
			File file1 = new File(DIRECTORY + relativePath + fileName);
			if (!file1.exists()) {
				file1.createNewFile();
			}
			FileOutputStream fos=new FileOutputStream(file1);
                        //每次接受1KB的字节数组,可以根据需要自己修改。注意有Integet.MAXVALUE的限制。
			buf = ByteBuffer.allocate(1024);
			while ((size = socketChannel.read(buf)) >= 0) {
				buf.flip();
				bytes = new byte[size];
				buf.get(bytes);
				fos.write(bytes);
				buf.clear();
			}
			fos.close();
		}
		RequestObject requestObject = new RequestObject(fileName, relativePath, absolutePath, md5,status);
		return requestObject;
	}

}

import java.io.Serializable;

public class RequestObject2 implements Serializable {
	private static final long serialVersionUID = 1L;
	private String filename;
	private String relativePath;
	private String absolutePath;
	private String md5;
	private byte[] contents;
	private String status;
	public RequestObject2(String filename, String relativePath, String absolutePath, String md5, byte[] contents,String status) {
		this.filename = filename;
		this.relativePath = relativePath;
		this.absolutePath = absolutePath;
		this.md5 = md5;
		this.contents = contents;
		this.status=status;
	}
	public String getFilename() {
		return filename;
	}
	public String getRelativePath() {
		return relativePath;
	}
	public String getAbsolutePath() {
		return absolutePath;
	}
	public String getMd5() {
		return md5;
	}
	public byte[] getContents() {
		return contents;
	}
	public String getStatus(){
	        return status;
	}
}

/*
*2017/07/12新增类,用于excute2方法
*想对于RequestObject,删除了byte[]
*/
import java.io.Serializable;

public class RequestObject implements Serializable {
	private static final long serialVersionUID = 1L;
	private String filename;
	private String relativePath;
	private String absolutePath;
	private String md5;
	private String status;
	public RequestObject(String filename, String relativePath, String absolutePath, String md5, String status) {
		this.filename = filename;
		this.relativePath = relativePath;
		this.absolutePath = absolutePath;
		this.md5 = md5;
		this.status = status;
	}
	public String getFilename() {
		return filename;
	}
	public String getRelativePath() {
		return relativePath;
	}
	public String getAbsolutePath() {
		return absolutePath;
	}
	public String getMd5() {
		return md5;
	}
	public String getStatus() {
		return status;
	}
}	

第三部分 客户端代码

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

public class Client2 {
        /*
        *2017/07/07添加该注释
        *用于存储要被上传文件的目录,即文章第一部分代码生成文件的位置
        */
	private static final String unpath = "G:\\temp\\unUploadedFilePath.txt";
	/*
	*2017/07/07添加该注释
	*待上传文件所在的目录
	*还有一个作用,用于获得相对路径
	*/
	private static final String pathPre = "D:\\upload\\";
	//2017/07/07添加该注释
	//服务器地址,因为是本地测试,所以用这个
	private static final String IPADDR = "127.0.0.1";
	//2017/07/07添加该注释
	//服务端口号
	private static final int PORT = 9999;
	Selector selector;

	public Client2() throws IOException {
		selector = Selector.open();
		new Thread(new SendDataRunnable()).start();
	}

	private class SendDataRunnable implements Runnable {
		private ClientHandler handler;

		public SendDataRunnable() {
			handler = new ClientHandler();
		}

		@Override
		public void run() {
			try {
				BufferedReader reader = new BufferedReader(new FileReader(new File(unpath)));
				String path = "";
				while ((path = reader.readLine()) != null && path.length() != 0) {
					SocketChannel socketChannel;
					socketChannel = SocketChannel.open();
					socketChannel.connect(new InetSocketAddress(IPADDR, PORT));
					socketChannel.configureBlocking(false);
					socketChannel.register(selector, SelectionKey.OP_READ);

					handler.sendData(socketChannel, path, pathPre);
					String response = handler.receiveData(socketChannel);
					System.out.println(response);
					socketChannel.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	public static void main(String[] args) throws IOException {
		Client2 client = new Client2();
	}

}


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

import org.apache.commons.codec.digest.DigestUtils;


public class ClientHandler {
	
	public void sendData(SocketChannel socketChannel,String path,String pathPre)throws Exception{
			System.out.println(path);
			String absoluteFilePath=getAbsoluteFilePath(path);
			String fileName=getFileName(absoluteFilePath);
			String relativeFilePath=getRelativeFilePath(absoluteFilePath, pathPre,fileName);
			System.out.println(absoluteFilePath);
			/*
			*2017/07/07添加该字段
			*用于区分文件是否正确读取
			*/
			String status="well";
			byte[] bytes=makeFileToBytes(absoluteFilePath);
			System.out.println(bytes.length);
			String md5=DigestUtils.md5Hex(bytes);
			/*
			*2017/07/07添加该注释
			*进行部分修改,增加status部分
			*/
			String fileInfo=new StringBuffer()
			                .append(status)
			                .append(";")
					.append(fileName)
					.append(";")
					.append(relativeFilePath)
					.append(";")
					.append(path)
					.append(";")
					.append(md5)
					.toString();
			System.out.println(fileInfo);
			ByteBuffer buffer = ByteBuffer.allocate(8 +fileInfo.getBytes().length+bytes.length);
			buffer.putInt(fileInfo.getBytes().length);
			buffer.put(fileInfo.getBytes());
			buffer.putInt(bytes.length);
			buffer.put(ByteBuffer.wrap(bytes));
			buffer.flip();
			/*
			*2017/07/07进行修改
			*之前没有外面的while循环,发现大文件server端一直接收失败
			*所以进行的添加。
			*/
			while (buffer.hasRemaining()) {
				socketChannel.write(buffer);
			}
			buffer.clear();
		// 关闭输出流防止接受时阻塞,就是告诉接收方本次的内容已经发完了,你不用等了
		socketChannel.socket().shutdownOutput();
	}
	/*
	*2017/07/12新增代码
	*/
	
		public void sendData2(SocketChannel socketChannel, String path, String pathPre) {
		System.out.println(path);
		String absoluteFilePath = getAbsoluteFilePath(path);
		File file = new File(absoluteFilePath);
		String fileName = getFileName(absoluteFilePath);
		String relativeFilePath = getRelativeFilePath(absoluteFilePath, pathPre, fileName);
		System.out.println(absoluteFilePath);
		String status = "well";
		String md5 = null;
		int bufferSize = 1024;//用于每次发送文件的大小设置,可以自行修改
		try {
			RandomAccessFile rafi = new RandomAccessFile(absoluteFilePath, "r");
			byte[] buf = new byte[bufferSize];
			int c = 0;
			try {
				c = rafi.read(buf);
				FileInputStream fis = new FileInputStream(new File(absoluteFilePath));
				md5 = DigestUtils.md5Hex(fis);
				fis.close();
			} catch (Exception e) {
				c = 0;
				status = "error";
				md5 = "0";
			}

			String fileInfo = new StringBuffer().append(status).append(";").append(fileName).append(";")
					.append(relativeFilePath).append(";").append(path).append(";").append(md5).toString();
			System.out.println(fileInfo);
			int len = c;
			//这里注意下12,因为之前是发送两个int类型,所以是8,现在是一个int一个long,所以12
			ByteBuffer buffer = ByteBuffer.allocate((int) (12 + fileInfo.getBytes().length + bufferSize));

			buffer.putInt(fileInfo.getBytes().length);
			buffer.put(fileInfo.getBytes());
			buffer.putLong(len > 0 ? file.length() : 0);
			/*
			*说下flag的作用
			*flag==true 表示这次发送的是一个buffersize的大小,可能后续还有字节数组没发送完成,需要继续判断;另外当c==0的时候,true的作用是用于后面发送文件的基本信息。
			*flag==false表示这次发送的不是一个buffersize的大小,也表示已经发送完成,无需后续判断
			*/
			boolean flag = true;
			while(c>0){
					if (c == buf.length) {
						buffer.put(ByteBuffer.wrap(buf));
						flag = true;
					} else {
						buffer.put(ByteBuffer.wrap(buf, 0, c));
						c = 0;
						flag = false;
					}
					buffer.flip();
					while (buffer.hasRemaining()) {
						socketChannel.write(buffer);
					}
					if(flag) {
						buffer.clear();
						c = rafi.read(buf);
					}

			}
			if(flag){
				buffer.flip();
				while (buffer.hasRemaining()) {
					socketChannel.write(buffer);
				}
			}
			rafi.close();

			buffer.clear();
			// 关闭输出流防止接受时阻塞,就是告诉接收方本次的内容已经发完了,你不用等了
			socketChannel.socket().shutdownOutput();
		} catch (Exception e) {
			e.printStackTrace();
			
		}

	}
	
	
	
	
        /*
        *2017/07/07添加注释
        *用于从类似AAA0000:D:\upload\addChannel.html格式的目录中读取真正的文件目录
        */
	private String getAbsoluteFilePath(String path){
		return path.substring(8);
	}
	/*
        *2017/07/07添加注释
        *用于从类似D:\upload\addChannel.html格式的目录中拿到被存储的相对路径(说的啥啊,自己都看不懂)
        *举个例子:
        *客户端文件目录D:\upload\test1\test2\addChannel.html,即为下面的absoluteFilePath
        *我想存储到服务器的 G:\NioRequest\test1\test2\addChannel.html
        *所以我要获得客户端目录的test1\test2\addChannel.html部分
        *然后拼接到服务器的目录G:\NioRequest\目录上
        *下面的pathPre是为前缀,上面例子中就是D:\upload\
        *因为我要的目录中不包括文件名,所以加了个文件名字段
        */
	private String getRelativeFilePath(String absoluteFilePath,String pathPre,String fileName){
		return absoluteFilePath.substring(pathPre.length(),absoluteFilePath.length()-fileName.length());
	}
	/*
	*2017/07/07添加注释
	*这个太简单,不解释
	*/
	private String getFileName(String path){
		return new File(path).getName();
	}
	
	/*
	*2017/07/07添加注释
	*将文件读取到字节数组,不是我自己写的,网上找到的
	*/
	
	private byte[] makeFileToBytes(String filePath){
		File file=new File(filePath);
		byte[] ret = null;  
        try {    
            FileInputStream in = new FileInputStream(file);  
            ByteArrayOutputStream out = new ByteArrayOutputStream(4096);  
            byte[] b = new byte[4096];  
            int n;  
            while ((n = in.read(b)) != -1) {  
                out.write(b, 0, n);  
            }  
            in.close();  
            out.close();  
            ret = out.toByteArray();  
        } catch (IOException e) {  
            // log.error("helper:get bytes from file process error!");  
            e.printStackTrace();  
        }  
        return ret;  
	}
	
	public String receiveData(SocketChannel socketChannel) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		String response = "";
		try {
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			byte[] bytes;
			int count = 0;
			while ((count = socketChannel.read(buffer)) >= 0) {
				buffer.flip();
				bytes = new byte[count];
				buffer.get(bytes);
				baos.write(bytes);
				buffer.clear();
			}

			bytes = baos.toByteArray();
			response = new String(bytes, "UTF-8");
//			socketChannel.socket().shutdownInput();
		} finally {
			try {
				baos.close();
			} catch (Exception ex) {
			}
		}
		return response;
	}

}

/*至此全部完成,注释不够多,部分代码是从网上找的。后期有时间会补全注释的,或者下次直接上最终使用的代码*/

《JAVA NIO服务器间连续发送文件(本地测试版).doc》

下载本文的Word格式文档,以方便收藏与打印。