其实NIO的主要用途是网络IO,在NIO之前java要使用网络编程就只有用Socket。而Socket是阻塞的,显然对于高并发的场景是不适用的。所以NIO的出现就是解决了这个痛点。
主要思想是把Channel通道注册到Selector中,通过Selector去监听Channel中的事件状态,这样就不需要阻塞等待客户端的连接,从主动等待客户端的连接,变成了通过事件驱动。没有监听的事件,服务器可以做自己的事情。
使用Selector的小例子
接下来趁热打铁,我们来做一个服务器接受客户端消息的例子:
首先服务端代码:
public class NIOServer { public static void main(String[] args) throws Exception { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); InetSocketAddress address = new InetSocketAddress("127.0.0.1", 6666); serverSocketChannel.bind(address); serverSocketChannel.configureBlocking(false); Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { if (selector.select(3000) == 0) { System.out.println("服务器等待3秒,没有连接"); continue; } Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectionKeys.iterator(); while (it.hasNext()) { SelectionKey selectionKey = it.next(); if (selectionKey.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } if (selectionKey.isReadable()) { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer buffer = (ByteBuffer) selectionKey.attachment(); socketChannel.read(buffer); System.out.println("from 客户端:" + new String(buffer.array())); } it.remove(); } } } }
|
客户端代码:
public class NIOClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); InetSocketAddress address = new InetSocketAddress("127.0.0.1", 6666); socketChannel.configureBlocking(false); boolean connect = socketChannel.connect(address); if(!connect){ while (!socketChannel.finishConnect()){ System.out.println("连接服务器需要时间,期间可以做其他事情..."); } } String msg = "hello java技术爱好者!"; ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes()); socketChannel.write(byteBuffer); System.in.read(); } }
|
接下来启动服务端,然后再启动客户端,我们可以看到控制台打印以下信息:
服务器等待3秒,没有连接 服务器等待3秒,没有连接 from 客户端:hello java技术爱好者! 服务器等待3秒,没有连接 服务器等待3秒,没有连接
|
通过这个例子我们引出以下知识点。
SelectionKey
在SelectionKey类中有四个常量表示四种事件,来看源码:
public abstract class SelectionKey { public static final int OP_READ = 1 << 0; public static final int OP_WRITE = 1 << 2; public static final int OP_CONNECT = 1 << 3; public static final int OP_ACCEPT = 1 << 4; }
|
附加的对象(可选),把通道注册到选择器中时可以附加一个对象。
public final SelectionKey register(Selector sel, int ops, Object att)
|
从selectionKey中获取附件对象可以使用attachment()方法
public final Object attachment() { return attachment; }
|