NO与NIO

1、阻塞IO:当一条线程执行read()或者write()方法时,这条线程会一直阻塞直到读取到了一些数据或者要写出去的数据已经全部写出,在这期间这条线程不能做任何其他的事情。

2、传统IO测试代码如下:当出现accept()、read()等方法是就会阻塞。

/**
 * 传统socket服务端
 * @author -zhengzx-
 *
 */
public class OioServer {

	@SuppressWarnings("resource")
	public static void main(String[] args) throws Exception {
		
		//创建socket服务,监听10101端口
		ServerSocket server=new ServerSocket(10101);
		System.out.println("服务器启动!");
		while(true){
			//获取一个套接字(阻塞)
			final Socket socket = server.accept();//(测试时可以通过:telnet 127.0.0.1 10101。进行测试)
			System.out.println("来个一个新客户端!");
				//业务处理
				handler(socket);
			}
	}
	
	/**
	 * 读取数据
	 * @param socket
	 * @throws Exception
	 */
	public static void handler(Socket socket){
			try {
				byte[] bytes = new byte[1024];
				InputStream inputStream = socket.getInputStream();
				
				while(true){
					//读取数据(阻塞)
					int read = inputStream.read(bytes);
					if(read != -1){
						System.out.println(new String(bytes, 0, read));
					}else{
						break;
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
				try {
					System.out.println("socket关闭");
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
	}
}

3、阻塞IO解决办法,可以通过线程池创建多线程,为每一次连接创建一个新的线程来执行。问题是对于长连接而言,线程过多时会严重消耗系统资源导致性能下降。比较适合短连接的应用。

public static void main(String[] args) throws Exception {
		
		//创建线程池(可以通过线程解决阻塞问题、问题:每次连接都会创建一个线程,特别是长连接时特别消耗系统资源)
		ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
		//创建socket服务,监听10101端口
		ServerSocket server=new ServerSocket(10101);
		System.out.println("服务器启动!");
		while(true){
			//获取一个套接字(阻塞)
			final Socket socket = server.accept();//(测试时可以通过:telnet 127.0.0.1 10101。进行测试)
			System.out.println("来个一个新客户端!");
			newCachedThreadPool.execute(new Runnable() {
				
				@Override
				public void run() {
					//业务处理
					handler(socket);
				}
			});
			
		}
	}

4、NIO的非阻塞模式(Java NIO有阻塞模式和非阻塞模式,阻塞模式的NIO除了使用Buffer存储数据外和IO基本没有区别)允许一条线程从channel中读取数据,通过返回值来判断buffer中是否有数据,如果没有数据,NIO不会阻塞,因为不阻塞这条线程就可以去做其他的事情,过一段时间再回来判断一下有没有数据。

*Selectors:Java NIO的selectors允许一条线程去监控多个channels的输入,你可以向一个selector上注册多个channel,然后调用selector的select()方法判断是否有新的连接进来或者已经在selector上注册时channel是否有数据进入。selector的机制让一个线程管理多个channel变得简单。

5、NIO示例代码如下:

public class NIOServerSocket {
	//定义一个socket入口
	private ServerSocketChannel serverSocket;
	//定义一个监听器
	Selector selector;
	public static void main(String[] args) throws IOException {
		NIOServerSocket nio =new NIOServerSocket();
		nio.initServer(8000);
		nio.listen();
	}
	public void initServer(int port) throws IOException {
		//获取一个serverSocket通道
		serverSocket = ServerSocketChannel.open();
		//设置为非阻塞状态(分为阻塞和非阻塞两种情况)
		serverSocket.configureBlocking(false);
		//将通道对应的serverSocketChannel绑定到端口上
		serverSocket.socket().bind(new InetSocketAddress(port));
		//获取一个通道管理器
		this.selector = Selector.open();
		//将通道管理器与通道进行绑定,并赋值SelectionKey.OP_ACCEPT事件,当事件
		//注册后,当事件到达后,select.select()会返回,如果没有返回,就一直阻塞。
		serverSocket.register(selector, SelectionKey.OP_ACCEPT);
	}
	public void listen() throws IOException {
		System.out.println("服务器启动");
		//轮询访问select.select()
		while(true) {
			//当事件到达时返回,否则一直阻塞
			selector.select();
			//获取selector中选中项的迭代器,相中的项为注册事件。
			Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
			while(iterator.hasNext()) {
				SelectionKey selectionKey = iterator.next();
				//删除已选的key,防止重复处理
				iterator.remove();
				handler(selectionKey);
			}
		}
	}
	public void handler(SelectionKey key) throws IOException {
		if(key.isAcceptable()) {
			handlerAccept(key);
		}else if(key.isReadable()) {
			handlerRead(key);
		}
	}
	public void handlerAccept(SelectionKey key) throws IOException {
		//获取以有的通道
		ServerSocketChannel channel = (ServerSocketChannel) key.channel();
		//获取和客户端连接的通道
		SocketChannel accept = channel.accept();
		//设置为非阻塞
		accept.configureBlocking(false);
		// 在这里可以给客户端发送信息哦
		System.out.println("新的客户端连接");
		//连接成功之后,为了读取客户端传送的消息,需要设置读权限
		accept.register(selector, SelectionKey.OP_READ);
	}
	public void handlerRead(SelectionKey key) throws IOException {
		//服务器可读取消息,获取事件发生的Socket通道
		SocketChannel channel = (SocketChannel) key.channel();
		//创建读取内容的缓存区buffer
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		int read = channel.read(buffer);
		if(read > 0) {
			byte[] array = buffer.array();
			String msg = new String(array).trim();
			System.out.println("服务端收到信息:" + msg);
			
			//会写
			ByteBuffer byteBuffer = ByteBuffer.wrap("success".getBytes());
			channel.write(byteBuffer);
		}else {
			System.out.println("客户端关闭");
			key.cancel();
		}
	}
}

6、同时selector.select()虽阻塞,但可以通过selector.wakeup()唤醒selector执行,也可以通过selector.select(int timeout)设置时间限制,timeout时间后唤醒selector。

7、NIO提高性能:添加多线程,一个线程对应一个selector,端口的监听可以单独创建一个selector。(既Netty的工作原理)

总结:NIO:允许你用一个单独的线程或几个线程管理很多个channels(网络的或者文件的),代价是程序的处理和处理IO相比更加复杂。如果你需要同时管理成千上万的连接,但是每个连接只发送少量数据,例如一个聊天服务器,用NIO实现会更好一些,相似的,如果你需要保持很多个到其他电脑的连接,例如P2P网络,用一个单独的线程来管理所有出口连接是比较合适的。

IO:如果你只有少量的连接但是每个连接都占有很高的带宽,同时发送很多数据,传统的IO会更适合

猜你喜欢

转载自blog.csdn.net/zhengzhaoyang122/article/details/81410115
NIO