JAVA NIO服務器間連續發送文件(本地測試版)

說在前面:給我的需求是實現從服務器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;
	}

}

/*至此全部完成,註釋不夠多,部分代碼是從網上找的。後期有時間會補全註釋的,或者下次直接上最終使用的代碼*/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章