Java NIO 知识梳理和例子

1,210 阅读3分钟

由于最近在学习Netty的相关知识,牵涉到NIO的相关知识,所以对NIO的基础进行梳理

1. 核心组件

读写在Java NIO中的最基本操作,可以创建Buffer然后从Channel中读取数据,同时也可以往Channel中写入数据。

image.png

  • Channels
  • Buffers
  • Selectors

在NIO中,我们使用通道和缓冲区。NIO中的所有I/O都是从一个通道开始的。数据总是从缓冲区写入到通道,从通道读取到缓冲区

2. Channels

Channeljava.nio.channels 包下面,下面来看一下继承关系:

image.png

2.1 SocketChannel

SocketChannel被用于TCP网络通信链接一个Channel,两种创建SocketChannel的方法:

  • 当有连接到达ServerSocketChannel的时候
  • 打开SocketChannel并且连接服务

让我们看看使用Selector的SocketChannel客户端-服务器通信框架图:

image.png

2.2 SocketChannel的操作

打开SocketChannel:

SocketChannel sc = SocketChannel.open();  
sc.connect(new InetSocketAddress("http://localhost", 8080));  

从SocketChannel读取数据:

ByteBuffer bb = ByteBuffer.allocate(84);  
int bytesRead = sc.read(bb);  

往SocketChannel写入数据:

String newData = "The new String is writing in a file ..." + System.currentTimeMillis();  
ByteBuffer bb= ByteBuffer.allocate(48);  
bb.clear();  
bb.put(newData.getBytes());  
bb.flip();  
while(bb.hasRemaining()) {  
    sc.write(bb);  
}  

关闭SocketChannel:

sc.close();    

2.3 ServerSocketChannel

ServerSocketChanne被用于TCP网络通信链接一个Channel,但是主要用于服务端

2.4 ServerSocketChannel操作

打开ServerSocketChannel:

ServerSocketChannel sc = ServerSocketChannel.open();  
sc.connect(new InetSocketAddress("http://localhost", 8080));  

监听端口进来的链接:

while(true){  
    SocketChannel sc = serverSocketChannel.accept();  
}  

Tips: 当有连接接入监听返回SocketChannel

从ServerSocketChannel读数据:

ByteBuffer bb = ByteBuffer.allocate(84);  
int bytesRead = sc.read(bb);  

往ServerSocketChannel写数据:

String newData = "The new String is writing in a file ..." + System.currentTimeMillis();  
ByteBuffer bb= ByteBuffer.allocate(48);  
bb.clear();  
bb.put(newData.getBytes());  
bb.flip();  
while(bb.hasRemaining()) {  
    sc.write(bb);  
}  

3. Buffers

下面看一下Buffer的继承关系:

image.png

4. Selectors

Selector用于使用单个线程处理多个通道。 因此,它需要更少的线程来处理通道。 对于操作系统来说,线程之间的切换开销很大。 因此,为了提高系统的效率

一个Selector处理多个Channel。

4.1 创建Selector

Selector selector = Selector.open();  

4.2 创建ServerSocketChannel

ServerSocketChannel serverSocket = ServerSocketChannel.open();  
InetSocketAddress hostAddress = new InetSocketAddress("localhost", 8080);  
serverSocket.bind(hostAddress);  

4.3 使用Selector选择Channel

在用选择器注册一个或多个通道时,我们可以调用select()方法之一。这个方法返回一个通道,该通道为我们想要执行的事件准备好了,例如 connect, read, write , accept。select方法有:

  • int select(): select()方法返回的整数值告知有多少Channel准备好进行通信。
  • int select(long TS): 和select()相同,除了它在最大TS(毫秒)时间段内阻塞
  • int selectNow(): 非阻塞,返回任何准备好的通道

一旦调用了任何一个select()方法,返回了值。然后通过调用Selector的selectedkeys()获取到selected key 集合:

Set<SelectionKey> selectedKeys = selector.selectedKeys();  

例子:

Set<SelectionKey> selectedKeys = selector.selectedKeys();  
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();  
while(keyIterator.hasNext()) {    
    SelectionKey key = keyIterator.next();  
    if(key.isConnectable()) {  
        // The connection was established with a remote server.  
    } else if (key.isAcceptable()) {  
        // The connection was accepted by a ServerSocketChannel.  
    } else if (key.isWritable()) {  
        //  The channel is ready for writing  
    } else if (key.isReadable()) {  
        // The channel is ready for reading  
    }  
    keyIterator.remove();  
}  

完整的选择环路框图如下所示:

image.png

4.4 使用案例

服务端代码:

package com.github.mxsm.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

/**
 * @author mxsm
 * @date 2022/3/10 23:26
 * @Since 1.0.0
 */
public class Server {

    public static void main(String[] args) throws Exception{
        Selector selector = Selector.open();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        InetSocketAddress hostAddress = new InetSocketAddress("localhost", 8080);
        ssc.bind(hostAddress);
        ssc.configureBlocking(false);
        int ops = ssc.validOps();
        SelectionKey selectKy = ssc.register(selector, ops, null);
        for (;;) {
            int noOfKeys = selector.select();
            if(noOfKeys <= 0){
                continue;
            }
            Set selectedKeys = selector.selectedKeys();
            Iterator itr = selectedKeys.iterator();
            while (itr.hasNext()) {
                SelectionKey ky = (SelectionKey) itr.next();
                if (ky.isAcceptable()) {
                    SocketChannel client = ssc.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);

                }
                else if (ky.isReadable()) {

                    SocketChannel client = (SocketChannel) ky.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(256);
                    client.read(buffer);
                    String output = new String(buffer.array()).trim();
                    System.out.println("接收客户端信息: " + output);
                    ByteBuffer byteBuffer1 = ByteBuffer.wrap(("时间戳:"+System.currentTimeMillis()).getBytes(
                        StandardCharsets.UTF_8));
                    client.write(byteBuffer1);
                }
                itr.remove();
            }
        }
    }
}

客户端代码:

package com.github.mxsm.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * @author mxsm
 * @date 2022/3/10 23:30
 * @Since 1.0.0
 */
public class Client {

    public static void main(String[] args) throws Exception{
        InetSocketAddress hA = new InetSocketAddress("localhost", 8080);
        SocketChannel client = SocketChannel.open(hA);
        System.out.println("The Client is sending messages to server...");

        for (;;) {
            ByteBuffer buffer = ByteBuffer.wrap(("客户端时间戳:"+System.currentTimeMillis()).getBytes(
                StandardCharsets.UTF_8));
            client.write(buffer);
            buffer.clear();
            ByteBuffer buffer1 = buffer.allocate(256);
            client.read(buffer1);
            System.out.println("接收服务器消息:"+new String(buffer1.array(), StandardCharsets.UTF_8).trim());
            TimeUnit.SECONDS.sleep(3);
        }

    }
}

运行结果:

nio运行结果.gif

我是蚂蚁背大象,文章对你有帮助点赞关注我,文章有不正确的地方请您斧正留言评论~谢谢