Java BOI与NIO超详细实例精讲
目录
- Java BIO
- 示例代码
- Java NIO
- 代码解读
Java BIO
阻塞IO,每个客户端链接都需要一个独立的线程处理,客户端链接没关闭时,线程链接处于阻塞状态,直到客户端链接关闭
如果客户端链接没有读取到数据,链接就会一直阻塞住,造成资源浪费
示例代码
开发一个ServerSocket服务端,通过telnet链接发送信息
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class BIOServerTest { public static void main(String[] args) throws Exception { ExecutorService pool = Executors.newCachedThreadPool(); ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端启动"); while (true) { System.out.println("等待链接..."); Socket socket = serverSocket.accept(); pool.execute(() -> { handler(socket); }); } } private static void handler(Socket socket) { try { System.out.println("有一个客户端接入:" + Thread.currentThread().getName()); byte[] bytes = new byte[1024]; //通过socket 获取输入流 InputStream inputStream = socket.getInputStream(); //循环读取客户端发送的数据 while (true) { System.out.println("等待读取数据..."); int read = inputStream.read(bytes); if (read != -1) { //输出客户端读取的数据 System.out.println("线程信息:" + Thread.currentThread().getName()); System.out.println(new String(bytes, 0, read)); } else { break; } } } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("关闭client链接:" + Thread.currentThread().getName()); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
通过telnet链接两个客户端,分别发送请求
从控制台打印信息可以看出每一个链接对应的线程都是独立的
等待链接...
有一个客户端接入:pool-1-thread-1
等待读取数据...
线程信息:pool-1-thread-1
aaa
等待读取数据...
线程信息:pool-1-thread-1
bbb
等待读取数据...
等待链接...
有一个客户端接入:pool-1-thread-2
等待读取数据...
线程信息:pool-1-thread-2
ccc
等待读取数据...
线程信息:pool-1-thread-2
ddd
等待读取数据...
Java NIO
非阻塞IO,通过Selector和Channel,实现一个线程维护多个链接
NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)
NOI是面向缓冲区编程的,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,增加的处理过程的灵活性,使用它可以提供非阻塞式的高伸缩性网络
NIO是通过事件驱动编程的,Selector会根据不同的事件,在各个通道上切换
Channel同时支持读写双向处理
Selector能够监测到多个注册的通到是否有事件发生,这样就可以只用一个线程去管理多个通道,也就是管理多个链接和请求。
只有在链接真正有读写事件发生时,才会进行读写,大大减少了系统开销,并且不必为每个链接创建一个线程
代码解读
- 当客户端链接时,会通过ServerSocketChannel得到SocketChannel
- 将SocketChannel注册到Selector上,一个selector上可以注册多个SocketChannel
- 通过socketChannel.register()方法注册
- 注册后返回一个SelectionKey,会和该Selector关联
- Selector监听select方法,返回有事件发生的通道的个数
- 进一步得到有事件发生的各个SelectionKey,通过SelectionKey反向获取SocketChannel
- 通过得到的channel完成业务处理
1)服务端代码
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 { public static void main(String[] args) throws Exception { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress(7000); serverSocketChannel.socket().bind(inetSocketAddress); serverSocketChannel.configureBlocking(false); Selector selector = Selector.open(); //注册客户端链接事件到selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int select = selector.select(1000L); if (select == 0) { System.out.println("等待1秒,没有事件发生"); continue; } //监听到相关事件发生,读取SelectionKey集合,遍历处理所有事件 Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); System.out.println("客户端链接成功,生成一个sockeChannel " + socketChannel.hashCode()); //将socketChannel设置为非阻塞 socketChannel.configureBlocking(false); //注册内容读取事件到selector,同时关联一个Buffer socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } if (key.isReadable()) { SocketChannel channel = null; try { channel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = (ByteBuffer) key.attachment(); int read = channel.read(byteBuffer); System.out.println("读取到数据: " + new String(byteBuffer.array(), 0, read)); } catch (Exception e) { if (channel != null) { channel.close(); } e.printStackTrace(); } } //手动从集合中移除当前的SelectionKey,防止重复操作 keyIterator.remove(); } } } }
2)客户端代码
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class NIOClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 7000); if (!socketChannel.connect(inetSocketAddress)){ while (!socketChannel.finishConnect()){ System.out.println("链接未完成,客户端不会阻塞...."); } } String str = "Hello,你好~~~"; ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(byteBuffer); System.in.read(); } }
控制台输出
等待1秒,没有事件发生
等待1秒,没有事件发生
等待1秒,没有事件发生
等待1秒,没有事件发生
等待1秒,没有事件发生
客户端链接成功,生成一个sockeChannel 60559178
读取到数据: Hello,你好~~~
等待1秒,没有事件发生
到此这篇关于Java BOI与NIO超详细实例精讲的文章就介绍到这了,更多相关Java BOI与NIO内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!