java的网络编程中,会波及到2种IO模型,一个就是BIO,即阻塞IO。一个是NIO,即非阻塞IO。 在默认的状况下,IO是阻塞IO,比方tomcat容器就是就是BIO。而redis, netty等反对高并发服务,都是基于NIO。NIO和BIO有3点区别:1:BIO是基于流(stream)的,比方:FileStream, SockectStream。而NIO是基于缓冲区buffer的,比方LongBuffer, ByteBuffer。2:BIO是阻塞的,NIO是非阻塞的。3:NIO有选择器(Selector),而BIO没有。NIO有三大组件:Channel:通道, Buffer:缓冲区, Selector:选择器。Channel:介绍:在NIO中,应用Channel进行通信,能够向Channel写入音讯,也能够从Channel读取音讯。Channel能够分为如下类型:ServerSocketChannel:服务端的网络Channel。能够绑定监听端口,能够承受客户端的网络连接。SocketChannel:示意网络连接Channel,能够从该Channel读取数据,也能够向Channel写入数据DatagramChannel:能通过UDP读写网络中的数据FileChannel 从文件中读写数据Buffer介绍:在NIO中,从channel读出数据或者向channel写入数据,都是通过buffer操作,这样能够提高效率。依照类型不同,Buffer也分为不同类型:比方:ByteBuffer, LongBuffer, IntBuffer, CharBuffer, FloatBuffer等。应用Buffer读写数据个别遵循以下四个步骤:1. 写入数据到Buffer2. 调用 flip() 办法3. 从Buffer中读取数据4. 调用clear()办法或者 compact()办法当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,须要通过flip()办法将Buffer从写模式切换到读模式。在读模式下,能够读取之前写入到buffer的所有数据。一旦读完了所有的数据,就须要清空缓冲区,让它能够再次被写入。有两种形式能清空缓冲区:调用clear()或compact()办法。clear()办法会清空整个缓冲区。compact()办法只会革除曾经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的前面。 Buffer 有三个重要属性:capacity,position,limit。position和limit的含意取决于Buffer处在读模式还是写模式。不论Buffer处在什么模式,capacity的含意总是一样的。capacity: 作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,须要将其清空(通过读数据或者革除数据)能力持续写数据往里写数据。position: 当你写数据到Buffer中时,position示意以后的地位。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前挪动到下一个可插入数据的Buffer单元。position最大可为capacity – 1。当读取数据时,也是从某个特定地位读。当将Buffer从写模式切换到读模式,position会被重置为0。 当从Buffer的position处读取数据时,position向前挪动到下一个可读的地位。limit:在写模式下,Buffer的limit示意你最多能往Buffer里写多少数据。写模式下,limit等于Buffer的capacity。当切换Buffer到读模式时, limit示意你最多能读到多少数据。因而,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)。Selector介绍:Selector(选择器)是Java NIO中可能检测一到多个NIO通道,并可能通晓通道是否为诸如读写事件做好筹备的组件。这样,一个独自的线程能够治理多个channel,从而治理多个网络连接。Selector反对的Channel的事件类型有:SelectionKey.OP_CONNECT, SelectionKey.OP_ACCEPT, SelectionKey.OP_READ, SelectionKey.OP_WRITE。服务端示例代码:
public void startNioServer() throws Exception { ServerSocketChannel serverChannel = ServerSocketChannel.open(); //关上通道 serverChannel.configureBlocking(false); // 非阻塞 ServerSocket serverSocket = serverChannel.socket(); serverSocket.bind(new InetSocketAddress(8090)); // 绑定端口 System.out.println("bind port: 8090"); Selector selector = Selector.open(); // 选择器 serverChannel.register(selector, SelectionKey.OP_ACCEPT); //注册到选择器 while (selector.select() > 0) { //判断事件筹备好的通道数量 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = it.next(); if (key.isAcceptable()) { //客户端连贯的事件 ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = serverSocketChannel.accept(); //新建设的客户端连贯 socketChannel.configureBlocking(false); //设置为非阻塞 socketChannel.register(selector, SelectionKey.OP_READ); //注册可读事件 } if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); //调配缓冲区,在堆内存调配 int len = 0; while ((len = socketChannel.read(buffer)) > 0) { buffer.flip(); System.out.println("NIO SERVER RECEIVE: " + new String(buffer.array(), 0, len)); buffer.clear(); } socketChannel.close(); } it.remove(); } } serverChannel.close();}
客户端示例代码:
public void startNioClient(int idx) throws Exception { SocketChannel socketChannel = SocketChannel.open(); //关上通道 socketChannel.configureBlocking(false); // 非阻塞 socketChannel.connect(new InetSocketAddress(8090)); // 连贯近程服务 while (!socketChannel.finishConnect()) { // 期待连贯实现 } String msg = "client-" + idx + " send message."; ByteBuffer buffer = ByteBuffer.allocate(1024); //调配缓冲区,默认是在堆上调配 buffer.put(msg.getBytes()); //写入数据 buffer.flip(); //反转为读模式 socketChannel.write(buffer); // 向通道写入信息 socketChannel.close();}