Java的NIO案例

Java的NIO案例教程

JavaNIO 教程中我们明白了 NIO 里面三个比较重要的元素,Buffer、Channel 和 Selector。它们之间相互协作,为 NIO 的高效提供了有力的保障。和 BIO 案例一样,它也需要客户端和服务端。需要网络通信,我们还是需要借助于 Socket。

案例

我们创建两个 Java 项目,一个是客户端,一个是服务端,代码结构如下:

NIO 服务端:

11 nio server 代码结构.png

NIO 客户端:

12 nio client 代码结构.png

服务端NioServer

package net.haicoder.server; import java.io.IOException; 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.util.Iterator; public class NioServer { private static ByteBuffer readBuf = ByteBuffer.allocate(1024); private static ByteBuffer writeBuf = ByteBuffer.allocate(1024); public static void main(String[] args) { int port = 9999; Selector selector; try { //打开多路复用器 selector = Selector.open(); //定义一个 Channel ServerSocketChannel channel = ServerSocketChannel.open(); //非阻塞模型 channel.configureBlocking(false); //绑定端口号 channel.bind(new InetSocketAddress(port)); //channel 注册到 Selector 上面 channel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务已经启动了 ......"); while (true) { //复用器开始监听 selector.select(); Iterator<SelectionKey> seletionKeys = selector.selectedKeys().iterator(); while (seletionKeys.hasNext()) { SelectionKey key = seletionKeys.next(); if (key.isValid()) { //监听客户端第一次端连接信息 if (key.isAcceptable()) { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); System.out.println("accept a client : " + sc.socket().getInetAddress().getHostName()); } else { read(key); } } seletionKeys.remove(); } } } catch (IOException e) { e.printStackTrace(); } } private static void read(SelectionKey key) { try { readBuf.clear(); //获取注册在通道里面的 key 对象 SocketChannel sc = (SocketChannel) key.channel(); int count = sc.read(readBuf); if (count == -1) { key.channel().close(); key.cancel(); return; } //有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位) readBuf.flip(); //6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据 byte[] bytes = new byte[readBuf.remaining()]; //7 接收缓冲区数据 readBuf.get(bytes); String body = new String(bytes).trim(); System.out.println("服务端接受到客户端请求的数据: " + body); //9 告诉客户端已收到数据 writeBuf.put(("你好,客户端,我已收到数据:" + body).getBytes()); //对缓冲区进行复位 writeBuf.flip(); //写出数据到服务端 sc.write(writeBuf); //清空缓冲区数据 writeBuf.clear(); } catch (Exception e) { e.printStackTrace(); } } }

客户端NioClient

package net.haicoder.client; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NioClient { private static final String host = "127.0.0.1"; private static final Integer port = 9999; public static void main(String[] args) { try { SocketChannel channel = SocketChannel.open(); channel.connect(new InetSocketAddress(host, port)); channel.configureBlocking(false); Selector selector = Selector.open(); channel.register(selector, SelectionKey.OP_READ, SelectionKey.OP_WRITE); //开启新的线程监听,否则的话,客户端在控制台输入信息不好操作。 new Thread(() -> { while (true) { try { //多路复用器开始监听 selector.select(); Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator(); while (selectionKeys.hasNext()) { SelectionKey selectionKey = selectionKeys.next(); if (selectionKey.isValid() && selectionKey.isReadable()) { //建立写缓冲区 ByteBuffer readBuf = ByteBuffer.allocate(1024); //2 获取之前注册的socket通道对象 SocketChannel sc = (SocketChannel) selectionKey.channel(); //3 读取数据 int count = sc.read(readBuf); if (count == -1) { selectionKey.channel().close(); selectionKey.cancel(); return; } //5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位) readBuf.flip(); //6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据 byte[] bytes = new byte[readBuf.remaining()]; //7 接收缓冲区数据 readBuf.get(bytes); String body = new String(bytes).trim(); System.out.println("收到服务端的数据:" + body); } selectionKeys.remove(); } } catch (IOException e) { e.printStackTrace(); } } }).start(); ByteBuffer writebuf = ByteBuffer.allocate(1024); while (true) { byte[] bytes = new byte[1024]; System.in.read(bytes); //把数据放到缓冲区中 writebuf.put(bytes); //对缓冲区进行复位 writebuf.flip(); //写出数据到服务端 channel.write(writebuf); //清空缓冲区数据 writebuf.clear(); } } catch ( IOException e) { e.printStackTrace(); } } }

项目启动的时候,我们先启动服务端,再启动客户端。如果先启动客户端,会报连接拒绝异常。因为服务端启动的时候,需要知道服务端的地址和端口号。然后在客户端输入 你好,嗨客网! 效果如下

13 NIO 运行结果.png

这样,客户端和服务端就通信了,我们看到服务端和客户端一直没有退出,一直启动着。

NIO案例总结

我们从代码中可以看到,代码的整体过程比较复杂。无论是客户端还是服务端,它们都依赖于 Selector、Channel 和 Buffer。将数据进行传递。有兴趣的同学可以实际操作一下。