Java BOI与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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
使用IDEA如何打包发布SpringBoot并部署到云服务器
这篇文章主要介绍了使用IDEA如何打包发布SpringBoot并部署到云服务器问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2023-12-12一篇文章带你解决 IDEA 每次新建项目 maven home directory 总是改变的问题
这篇文章主要介绍了一篇文章带你解决 IDEA 每次新建项目 maven home directory 总是改变的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-09-09使用Jenkins一键打包部署SpringBoot项目的步骤详解
任何简单操作的背后,都有一套相当复杂的机制,本文将以SpringBoot应用的在Docker环境下的打包部署为例,详细讲解如何使用Jenkins一键打包部署SpringBoot应用,文中通过图文结合讲解的非常详细,需要的朋友可以参考下2023-11-11
最新评论