Java Nio实现socket读写文件

1 篇文章 0 订阅

服务器端

package com.nio;

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 NioServer {
	Selector selector = null;
	ServerSocketChannel serverSocketChannel = null;
	private NioserverHandler handler;
	public NioServer() 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 NioServer(NioserverHandler 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 NioServer(new NioserverHandler());
	}
}

服务器端操作

package com.nio;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <p>
 * 这样的写法倾向于mina过度
 * </p>
 * @author niming
 *
 */
public class NioserverHandler {
	
	private final static Logger logger = Logger.getLogger(NioserverHandler.class.getName());
	
	private final static String DIRECTORY = "D:/NioRequest";
	
	/**
	 * 这里边我们处理接收和发送
	 * 
	 * @param serverSocketChannel
	 */
	public void excute(ServerSocketChannel serverSocketChannel) {
		SocketChannel socketChannel = null;
		try {
			socketChannel = serverSocketChannel.accept(); // 等待客户端连接
			
			RequestObject requestObject = receiveData(socketChannel);// 接数据 
			logger.log(Level.INFO,requestObject.toString());
			writeFile(DIRECTORY,requestObject); // 写文件
			String response = "File " + new String(requestObject.getFileName()) + " SUCCESS......";
			responseData(socketChannel, response);
			logger.log(Level.INFO, response);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * <p>读取通道中的数据到Object里去</p>
	 * 
	 * @param socketChannel
	 * @return
	 * @throws IOException
	 */
	public RequestObject receiveData(SocketChannel socketChannel) throws IOException {
		
		// 文件名长度
		int nameLength = 0;
		// 文件名
		byte[] fileName = 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();
			nameLength = capacity;
		}
		
		// 拿文件名,相信文件名一次能够读完,如果你文件名超过1K 你有病了
		buf = ByteBuffer.allocate(capacity);
		size = socketChannel.read(buf);
		if (size >= 0) {
			buf.flip();
			bytes = new byte[size];
			buf.get(bytes);
			fileName = bytes;
			buf.clear();
		}
		
		// 拿到文件长度
		capacity = 4;
		buf = ByteBuffer.allocate(capacity);
		size = socketChannel.read(buf);
		if (size >= 0) {
			buf.flip();
			// 文件长度是可要可不要的,如果你要做校验可以留下
			contentLength = buf.getInt();
			buf.clear();
		}
		
		// 用于接收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();
		
		RequestObject requestObject = new RequestObject(nameLength, fileName, contentLength, contents);
		
		return requestObject;
	}
	
	/**
	 * 把接收的数据写到本地文件里
	 * 
	 * @param drc 本地问价目录
	 * @param requestObject
	 * @throws UnsupportedEncodingException 
	 * @throws IOException 
	 */
	public static void writeFile(String drc,RequestObject requestObject) throws UnsupportedEncodingException {
		File file = new File(drc + File.separator + new String(requestObject.getFileName(),"utf-8"));
		FileOutputStream os = null;
		try {
			os = new FileOutputStream(file);
			os.write(requestObject.getContents());
			os.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (os != null)
					os.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 我们接受数据成功后要给客户端一个反馈
	 * 如果是java的客户端我们可以直接给对象,如果不是最好还是给文本或字节数组
	 * @param socketChannel
	 * @param response
	 */
	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();
		}
	}
	
}


接收文件的封装类

package com.nio;

import java.io.Serializable;
import java.util.Arrays;

/**
 * <p>
 * 此类用于我们接收client端发来信息
 * 使用字节数组可以兼容字符串,文件,图片
 * </p>
 * @author niming
 *
 */
public class RequestObject implements Serializable {
	
	private static final long serialVersionUID = -765860549787565035L;
	
	/** 文件名长度 */
	private int nameLength;
	/** 文件名 */
	private byte[] fileName;
	/** 文件长度 */
	private int contentLength;
	/** 文件内容 */
	private byte[] contents;
	
	public RequestObject() {}
	
	public RequestObject(int nameLength, byte[] fileName, int contentLength,
			byte[] contents) {
		this.nameLength = nameLength;
		this.fileName = fileName;
		this.contentLength = contentLength;
		this.contents = contents;
	}

	public int getNameLength() {
		return nameLength;
	}

	public void setNameLength(int nameLength) {
		this.nameLength = nameLength;
	}

	public byte[] getFileName() {
		return fileName;
	}

	public void setFileName(byte[] fileName) {
		this.fileName = fileName;
	}

	public int getContentLength() {
		return contentLength;
	}

	public void setContentLength(int contentLength) {
		this.contentLength = contentLength;
	}

	public byte[] getContents() {
		return contents;
	}

	public void setContents(byte[] contents) {
		this.contents = contents;
	}

	@Override
	public String toString() {
		return "[ nameLength : " + nameLength 
		+ " ,fileName : " + Arrays.toString(fileName)
		+ " ,contentLength : " + contentLength
		+ " ,contents : " +contentLength+"]";
	}
}


客户端socket

package com.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;

public class NioClient {
	
	private final static Logger logger = Logger.getLogger(NioClient.class.getName());
	
	public static void main(String[] args) {
		//for(int i =0; i< 10 ;i++)
		new Thread(new MyRunnable(new NioClientHandler())).start();
	}
	
	private static final class MyRunnable implements Runnable {
		
		private static final String FILEPATH =  "F:\\ThinkinJava4.zip";
		
		private NioClientHandler handler;
		private MyRunnable(NioClientHandler handler) {
			this.handler = handler;
		}

		public void run() {
			SocketChannel socketChannel = null;
			
				try {
					// 和ServerSocket连接与传统的socket类似
					// 无非就是换成了套接字通道
					socketChannel = SocketChannel.open();
					
					SocketAddress socketAddress = new InetSocketAddress(
							InetAddress.getLocalHost(), 9999);
					
					socketChannel.connect(socketAddress);
					
					long start = System.currentTimeMillis();
					// 发送数据
					handler.sendData(socketChannel, FILEPATH, "ThinkinJava4.zip");
					// 接收服务器反馈的信息
					String response = handler.receiveData(socketChannel);
					
					long end = System.currentTimeMillis();
					logger.log(Level.INFO, response + " 用时 : " + (end-start) + "ms");
				} catch (IOException e) {
					logger.log(Level.SEVERE, null, e);
				} finally {
					try {
						socketChannel.close();
					} catch (Exception ex) {
					}
				}
		}
	}
}


客户端操作封装类

package com.nio;

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;

public class NioClientHandler {
	/**
	 * 向服务器发送数据
	 * 
	 * @param socketChannel
	 * @param filepath
	 * @param filename
	 * @throws IOException
	 */
	public void sendData(SocketChannel socketChannel,String filepath, String filename) throws IOException {
		byte[] bytes = makeFileToByte(filepath);
		// ByteBuffer的缓冲区长度,4字节的文件名长度+文件名+4字节文件长度+文件
		ByteBuffer buffer = ByteBuffer.allocate(8 + filename.getBytes().length
				+ bytes.length);
		buffer.putInt(filename.length());// 文件名长度
		buffer.put(filename.getBytes()); // 文件名
		buffer.putInt(bytes.length); // 文件长度
		buffer.put(ByteBuffer.wrap(bytes));// 文件
		buffer.flip();// 把缓冲区的定位指向开始0的位置 清除已有标记
		socketChannel.write(buffer);
		buffer.clear();
		// 关闭输出流防止接收时阻塞, 就是告诉接收方本次的内容已经发完了,你不用等了
		socketChannel.socket().shutdownOutput();
	}
	
	/**
	 * 接收服务器相应的信息
	 * 
	 * @param socketChannel
	 * @return
	 * @throws IOException
	 */
	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;
	}

	private byte[] makeFileToByte(String fileFath) throws IOException {
		File file = new File(fileFath);
		FileInputStream fis = new FileInputStream(file);
		int length = (int) file.length();
		byte[] bytes = new byte[length];
		int temp = 0;
		int index = 0;
		while(true){
			index = fis.read(bytes,temp,length - temp);
			if(index <= 0 )
				break;
			temp += index;
		}
		fis.close();
		return bytes;
	}
	
}


虽然Nio属于非阻塞Io,提高了不少的效率,但是针对多线程我们还是要实现自己的控制,再次推荐另外一个基于Nio的实现框架mina,后续学习


 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值