NIO同步阻塞与同步非阻塞

BIO与NIO

IO与NIO区别:其本质就是阻塞和非阻塞的区别。

阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,就会一直等待,直到传输完毕为止。

非阻塞概念:应用程序直接可以获取已经准备就绪好的数据,无需等待。

IO为同步阻塞形式,NIO为同步非阻塞形式,NIO并没有实现异步,在JDK1.7升级NIO库包,支持异步非阻塞。

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的链接请求都会注册到选择器上,选择器轮询到连接有IO请求时才启动一个线程进行处理。

AIO:异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由OS先完成在通知服务器应用去启动线程进行处理。

同步时,应用程序会直接参与IO读写操作,并且我们的应用程序会直接阻塞到某一个方法上,直到数据准备就绪,或者采用轮训的策略实时检查数据的就绪状态,如果就绪则获取数据。

异步时,则所有的IO读写操作交给操作系统,与我们的应用程序没有直接关系,我们程序不需要关系IO读写,当操作系统完成了IO读写操作时,会给我们应用程序发送通知,我们的应用程序直接拿走数据即可。

伪异步

由于BIO一个客户端需要一个线程去处理,因此我们进行优化,后端使用线程池来处理多个客户端的请求接入,形成客户端个数M,线程池最大的线程数N的比例关系,其中M可以远远大于N,通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。

//服务器端
public class TCPServer {
	public static void main(String[] args) throws Exception {
		ExecutorService es = Executors.newCachedThreadPool();
		ServerSocket serverSocket = new ServerSocket(8989);
		try {
			while(true) {
				Socket accept = serverSocket.accept();
				es.execute(new Runnable() {
					public void run() {
						try {
							InputStream is = accept.getInputStream();
							byte[] bs = new byte[1024];
							int length = is.read(bs);
							System.out.println(new String(bs,0,length));
						} catch (Exception e) {
							
						}
					}
				});
			}
		} catch (Exception e) {
			
		}finally {
			serverSocket.close();
		}
	}
}



//客户端
public class TCPClient {
	public static void main(String[] args) throws Exception {
		Socket s = new Socket("127.0.0.1", 8989);
		OutputStream os = s.getOutputStream();
		os.write("你好".getBytes());
		s.close();
	}
}

什么是阻塞

应用程序在获取网络数据的时候,如果网络传输很慢,那么程序就一直等着,直接到传输完毕。

什么是非阻塞

应用程序直接可以获取已经准备好的数据,无需等待。

IO为同步阻塞形式,NIO为同步非阻塞形式,NIO没有实现异步,在JDK1.7之后,升级NIO库包,支持异步非阻塞通讯模型。

//异步非阻塞
public class Client {
	public static void main(String[] args) throws Exception {
		//创建通道
		SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080));
		//切换异步非阻塞模式
		sc.configureBlocking(false);
		//设置缓冲去大小
		ByteBuffer bb = ByteBuffer.allocate(1024);
		//获取键盘输入的值
		Scanner s = new Scanner(System.in);
		while(s.hasNext()) {
			String str = s.next();
			//把获取的值写入缓冲区中
			bb.put(str.getBytes());
			bb.flip();
			//把缓冲区中的值写入通道中
			sc.write(bb);
			bb.clear();
		}
		sc.close();
	}
}



public class Server {
	public static void main(String[] args) throws Exception {
		//创建通道
		ServerSocketChannel ssc = ServerSocketChannel.open();
		//切换到异步非阻塞模式
		ssc.configureBlocking(false);
		//绑定链接
		ssc.bind(new InetSocketAddress(8080));
		//获取选择器
		Selector open = Selector.open();
		//将通道注册到选择器,并指定监听接受事件
		ssc.register(open, SelectionKey.OP_ACCEPT);
		//轮训式获取选择已经准备就绪的事件
		while(open.select() > 0) {
			//获取当前选择器所有注册的监听事件
			Iterator<SelectionKey> it = open.selectedKeys().iterator();
			while(it.hasNext()) {
				//获取准备就绪的事件
				SelectionKey sk = it.next();
				//判断是什么事件准备就绪
				if(sk.isAcceptable()) {
					//接受就绪,获取客户端连接
					SocketChannel sc = ssc.accept();
					//设置非阻塞异步模式
					sc.configureBlocking(false);
					//将通道注册到服务器上
					sc.register(open, SelectionKey.OP_READ);
				} else if(sk.isReadable()) {
					//获取当前选择器就绪的通道
					SocketChannel s =  (SocketChannel) sk.channel();
					ByteBuffer bb = ByteBuffer.allocate(1024);
					int len = 0;
					while((len = s.read(bb)) > 0) {
						bb.flip();
						System.out.println(new String(bb.array(),0,len));
						bb.clear();
					}
				}
			}
			it.remove();
		}
	}
}

选择KEY

SelectionKey.OP_CONNECT     可连接

SelectionKey.OP_ACCPT           可接受连接

SelectionKey.OP_READ             可读

SelectionKey.OP_WRITE           可写

如果要监听多种事件,可以用 | 操作符将常量连接起来。

猜你喜欢

转载自blog.csdn.net/qq_38065439/article/details/87971256