JAVA NIO

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. 写入数据到Buffer
2. 调用 flip() 方法
3. 从Buffer中读取数据
4. 调用clear()方法或者 compact()方法

当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。    
Buffer 有三个重要属性:capacity,position,limit。position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。
![image](/img/bVI7kq)

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 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();
}

你可能感兴趣的:(java,nio,socket)