> 文章列表 > IO线程模型

IO线程模型

IO线程模型

文章目录

  • IO线程模型
    • 一、BIO
      • 1、概念
      • 2、Demo
        • 2.1、Demo1.0
        • 2.2、Demo2.0
        • 2.3、小结
    • 二、NIO
      • 1、概念
      • 2、Demo
        • 2.1、Demo1.0
        • 2.2、Demo2.0

IO线程模型

一、BIO

1、概念

       BIO 全称 Block-IO 是一种**同步且阻塞**的通信模式。是一个比较传统的通信方式,模式简单,使用方便。但并发处理能力低,通信耗时,依赖网速。

       同步: 可以理解为干这件事中间,不能干其他事
       阻塞: 可以理解为有事把游戏暂停了,干完事了再来继续游戏

概念不好理解,直接上Demo

2、Demo

2.1、Demo1.0

public class SocketServer {private static void handler(Socket clientSocket) throws Exception {byte[] bytes = new byte[1024];System.out.println("准备read。。。");int read = clientSocket.getInputStream().read(bytes);System.out.println("read完毕!");if(read != -1){System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));}}public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(8001);while(true){System.out.println("等待连接。。。");// 阻塞住了Socket clientSocket = serverSocket.accept();System.out.println("有客户端连接了。。。");handler(clientSocket);}}
}

       先理解一下这段代码里面的 Socket clientSocket = serverSocket.accept(); int read = clientSocket.getInputStream().read(bytes);,这两端代码都是阻塞的,也就是当执行到这里的时候,就会卡住了,暂时不会执行下面的东西了

启动的时候,这里控制台输出完等待连接以后,就会卡住了

IO线程模型

然后这里使用一个Telnet的东西,百度一下即可
IO线程模型
使用Telnet搭建一个客户端连接到上面的服务端中

IO线程模型

我们这个时候再开一个Telnet客户端连接到上面的服务端

IO线程模型

这时候再第一个Telnet中随便摁下键盘,你会发现控制台输出

IO线程模型

我觉的这里有两点:

  • 一是当你两个Telnet连接的时候,只有第一个Telnet先显示连接,另一个Telnet没有显示,同一时间只能处理一件事(这感觉是同步
  • 二是第一个Telnet接收完之后又显示出来了第二个Telnet的连接信息,说明第二个Telnet没有被抛弃,等到第一个搞完了再轮到它(这应该就是阻塞

现在应该能体验到这种方式比较局限,它适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。

2.2、Demo2.0

稍微改进一些

public class SocketServer {private static void handler(Socket clientSocket) throws Exception {byte[] bytes = new byte[1024];System.out.println("准备read。。。");int read = clientSocket.getInputStream().read(bytes);System.out.println("read完毕!");if(read != -1){System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));}}public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(8001);while(true){System.out.println("等待连接。。。");// 阻塞住了Socket clientSocket = serverSocket.accept();System.out.println("有客户端连接了。。。");new Thread(() -> {try {handler(clientSocket);}catch (Exception e){e.printStackTrace();}}).start();}}
}

修改的地方也就是多开了线程去连接下一个客户端,也就是每连接一个客户端就会新开一个线程(也就是打破了阻塞),使用两个Telnet试一试
IO线程模型

并且当你在两个Telnet嗯东西的时候,控制台也有反应

2.3、小结

两种方式都是BIO:

  1. 第一种就是在服务端处理完第一个客户端的所有事件之前,无法为其他服务端提供服务
  2. 第二种弥补了第一种的缺点,但是会产生大量空闲线程,徒增压力,浪费资源

这样通过通过多线程的方式,确实可以解决一些问题,但是还是会带来一些新的问题,所以要寻求更好的解决方法

二、NIO

1、概念

      Java NIO,全程 Non-Block IO ,是 Java SE 1.4 版以后,针对网络传输效能优化的新功能。是一种非阻塞同步的通信模式。

2、Demo

2.1、Demo1.0

public class NioServer {// 保存客户端连接static List<SocketChannel> channelList = new ArrayList<>();public static void main(String[] args) throws IOException {// 创建NIO ServerSocketChannel,与BIO的serverSocket类似ServerSocketChannel serverSocket = ServerSocketChannel.open();serverSocket.socket().bind(new InetSocketAddress(8001));// 设置ServerSocketChannel为非阻塞serverSocket.configureBlocking(false);System.out.println("服务启动成功");while (true) {// 非阻塞模式accept方法不会阻塞,否则会阻塞// NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数SocketChannel socketChannel = serverSocket.accept();if (socketChannel != null) { // 如果有客户端进行连接System.out.println("连接成功");// 设置SocketChannel为非阻塞socketChannel.configureBlocking(false);// 保存客户端连接在List中channelList.add(socketChannel);}// 遍历连接进行数据读取 10w - 1000 读写事件Iterator<SocketChannel> iterator = channelList.iterator();while (iterator.hasNext()) {SocketChannel sc = iterator.next();ByteBuffer byteBuffer = ByteBuffer.allocate(128);// 非阻塞模式read方法不会阻塞,否则会阻塞int len = sc.read(byteBuffer);// 如果有数据,把数据打印出来if (len > 0) {System.out.println(Thread.currentThread().getName() + " 接收到消息:" + new String(byteBuffer.array()));} else if (len == -1) { // 如果客户端断开,把socket从集合中去掉iterator.remove();System.out.println("客户端断开连接");}}}}
}

自己看懂就可以,看不懂,看我下面

IO线程模型

怎么去理解这个非阻塞是什么意思呢?你Debug启动一下项目然后,在SocketChannel socketChannel = serverSocket.accept();打一个断点,然后点下面那个按钮,你就会发现它会一直循环,这就是非阻塞了

IO线程模型
然后当我们连接一个客户端的时候

IO线程模型
IO线程模型

这里就可以看出来,accept后,通过不断的轮询channelist中的连接,有则打印出来,没有就继续accept,没有中间阻塞的情况,这里也没有使用多线程,也就是说用一个线程,完成了BIO那里开多个线程完成的事情

这里可以同时开几个Telnet,然后Debug启动服务端,进行一下联调,差不多就能理解点了

这里会发现还有优化的空间,如果我们这里连接了10w个客户端,但是只有1w个客户端有真正的事件发生,我们的关注点应该在那1w个上面,如果我们每次都要去遍历这10w个客户端的话,很头疼的

2.2、Demo2.0

这个就是解决了上面的那个问题,使用了多路复用器

public class NioSelectorServer {public static void main(String[] args) throws IOException {int OP_ACCEPT = 1 << 4;System.out.println(OP_ACCEPT);// 创建NIO ServerSocketChannelServerSocketChannel serverSocket = ServerSocketChannel.open();serverSocket.socket().bind(new InetSocketAddress(8001));// 设置ServerSocketChannel为非阻塞serverSocket.configureBlocking(false);// 打开Selector处理Channel,即创建epollSelector selector = Selector.open();// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣SelectionKey selectionKey = serverSocket.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务启动成功");while (true) {// 阻塞等待需要处理的事件发生 已注册事件发生后,会执行后面逻辑selector.select();// 获取selector中注册的全部事件的 SelectionKey 实例Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();// 遍历SelectionKey对事件进行处理while (iterator.hasNext()) {SelectionKey key = iterator.next();// 如果是OP_ACCEPT事件,则进行连接获取和事件注册if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel socketChannel = server.accept();socketChannel.configureBlocking(false);// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件SelectionKey selKey = socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("客户端连接成功");} else if (key.isReadable()) {  // 如果是OP_READ事件,则进行读取和打印SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(128);int len = socketChannel.read(byteBuffer);// 如果有数据,把数据打印出来if (len > 0) {System.out.println(Thread.currentThread().getName() +  "接收到消息:" + new String(byteBuffer.array()));} else if (len == -1) { // 如果客户端断开连接,关闭SocketSystem.out.println("客户端断开连接");socketChannel.close();}}//从事件集合里删除本次处理的key,防止下次select重复处理iterator.remove();}}}
}

这个为了解决上面那个问题,加入了多路复用,为不让他做一些无用的循环遍历,抛弃了channellist集合,把连接都注册到多路复用器里面

IO线程模型

我大致理解的就是这样子的,可能不太周到

这样子处理的话,如果连接了10w个连接,当有事件的连接过来的时候,就会去处理该连接,而不会全局的循环,也就避免了时间上的消耗

还有一个AIO,之后遇到了再总结吧!