• Java网络编程----通过实现简易聊天工具来聊聊NIO


    前文我们说过了BIO,今天我们聊聊NIO。
    NIO 是什么?NIO官方解释它为New lO,由于其特性我们也称之为,Non-Blocking IO。这是jdk1.4之后新增的一套IO标准。
    为什么要用NIO呢?
    我们再简单回顾下BIO:
    阻塞式IO,原理很简单,其实就是多个端点与服务端进行通信时,每个客户端有一个自己的socket,他们与服务端的serverSocket进行连接,服务端为每一个客户端socket 生成一个对应的socket。
    这样客户端就可以通过自己的socket进行与服务端的读写,而服务端也可以通过对应的socket与客户端进行读写。
    由于这些socket需要一直持有来等待接听和连接,所以只能阻塞式的原地等待。这大大降低了服务器的性能上限。这就像是一个服务员只能对接自己当前的客人,无法接收多个客人的需求。
    那怎么解决呢?(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )

    我先举个例子,酒店的厨房只有一个厨师,厨师并不会依次对接每一个客人,满足客人需求后,再对接下一个客人,而是会收到一份包含当前所有客户要求的菜品单。
    然后开始处理菜品单,同时收集新的需求到新的菜品单中。当菜品单中所有菜品处理完成后,清空旧的菜品单,处理新的菜品单,如此反复循环。他并不会等待第一个客人的菜都做好了,才接收第二个客人要求的菜品。
    我们来看看这个是如何实现的:

    1、创建selector----->相当于厨师
    2、创建服务端socketChannel
    3、将服务端socketChannel绑定到selector中,同时接收accept事件------->相当于厨师收到的菜品单
    4、开始循环,处理事件队列中收到的所有事件------->相当于厨师处理客户的诉求
    5、如果有accept事件,就把accept事件中新连接channel也绑定到selector中,同时接收read事件。
    6、处理完所有事件后清空事件队列中的事件 ------->厨师处理完所有菜品后,清空菜品单(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )
    这里本质上其实就是绑定事件,监听请求,处理事件,只是换成批量监听,和批量处理了。
    服务端代码:

    复制代码
     1 package com.example.demo.learn.tcp;
     2 
     3 import java.io.IOException;
     4 import java.net.InetSocketAddress;
     5 import java.nio.ByteBuffer;
     6 import java.nio.channels.SelectionKey;
     7 import java.nio.channels.Selector;
     8 import java.nio.channels.ServerSocketChannel;
     9 import java.nio.channels.SocketChannel;
    10 import java.util.Set;
    11 
    12 /**
    13  * @discription
    14  */
    15 public class NIOServer {
    16     static Object obj;
    17 
    18     public static void main(String[] args) throws IOException {
    19         Selector selector = Selector.open();
    20         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    21         serverSocketChannel.socket().bind(new InetSocketAddress(9999));
    22         serverSocketChannel.configureBlocking(false);
    23         serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    24         while (true) {
    25             selector.select();//注意这里
    26             Set allKey = selector.selectedKeys();
    27             for (SelectionKey selectionKey : allKey) {
    28                 if (selectionKey.isAcceptable()) {
    29                     ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
    30                     if (serverChannel == serverSocketChannel) {
    31                         int a = 1;
    32                     }
    33                     SocketChannel client = serverChannel.accept();
    34                     client.configureBlocking(false);
    35                     client.register(selector, SelectionKey.OP_READ);
    36                     obj = client;
    37                 } else if (selectionKey.isReadable()) {
    38                     SocketChannel client = (SocketChannel) selectionKey.channel();
    39                     if (client == obj) {
    40                         int a = 1;
    41                     }
    42                     ByteBuffer buffer = ByteBuffer.allocate(1024);
    43                     client.read(buffer);
    44                     buffer.flip();
    45                     byte[] bytes = new byte[buffer.remaining()];
    46                     buffer.get(bytes);
    47                     System.out.println("received msg :" + new String(bytes));
    48                     ByteBuffer responseBuffer = ByteBuffer.wrap("Hello , client!".getBytes());
    49                     client.write(responseBuffer);
    50                 }
    51             }
    52             allKey.clear();
    53         }
    54     }
    55 }
    复制代码

    为了清晰,客户端代码我们仍然采用BIO模式中的客户端代码:

    复制代码
     1 public class TCPClient {
     2     public static void main(String[] args) throws IOException {
     3         Socket clientSocket=new Socket("127.0.0.1",9999);
     4         ChatThread chatThread = new ChatThread(clientSocket);
     5         new Thread(chatThread).start();
     6 
     7     }
     8 }
     9 
    10 class ChatThread implements Runnable {
    11     private Socket clientSocket;
    12 
    13     ChatThread(Socket clientSocket) {
    14         this.clientSocket = clientSocket;
    15     }
    16 
    17     @Override
    18     public void run() {
    19         try {
    20             OutputStream os = clientSocket.getOutputStream();
    21             SayThread sayThread = new SayThread(os);
    22             new Thread(sayThread).start();
    23 
    24             InputStream is = clientSocket.getInputStream();
    25             byte[] buffer = new byte[1024];
    26             int len = is.read(buffer);
    27             while (len > 0) {
    28                 String msg = new String(buffer, 0, len);
    29                 System.out.println("");
    30                 System.out.println("receive server msg :");
    31                 System.out.println(msg);
    32                 System.out.println("");
    33                 len = is.read(buffer);
    34             }
    35             clientSocket.close();
    36 
    37         } catch (Exception ex) {
    38             //logs
    39         }
    40 
    41     }
    42 }
    43 
    44 class SayThread implements Runnable {
    45     private OutputStream os;
    46 
    47     SayThread(OutputStream outputStream) {
    48         this.os = outputStream;
    49     }
    50 
    51     @Override
    52     public void run() {
    53         try {
    54             os.write("client connect success!!!".getBytes());
    55             Scanner inputScanner = new Scanner(System.in);
    56             while (true) {
    57                 String str = inputScanner.nextLine();
    58                 os.write(str.getBytes());
    59                 os.flush();
    60             }
    61 
    62         } catch (Exception ex) {
    63             //logs
    64         }
    65 
    66     }
    67 }
    复制代码

    先运行服务端,然后运行客户端,我们来看下效果:

    如果到这里你还没看懂,有一点点迷,那么你只要记住整个NIO中我们其实只要关注三个核心东西,然后再结合前文是橙色粗体字的代码思路来理解下:
    Channel:通道,类似于BIO中的Socket,我们可以通过它来进行读写;(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )
    Selector:选择器,我们将Channel和他需要关心的事件绑定起来,当事件响应时,激活对应的Channel(我们常用到的监听事件,就是连接accept事件和读read事件);
    Buffer:缓存,我们用来配合Channel完成高效读写的对象(这个具体内容比较复杂而且比较多,我后边会写文章专门介绍,这里先不用关心);

    整体的结构图大概是这个样子的:

    到这里,我们基本就对NIO有一个大致的了解了,那么NIO的代码中完全不存在阻塞等待么?
    答案是还会发生阻塞等待,注意代码中的红色标记(如图,通过debug,我们也会发现线程阻塞到了这里),当线程执行到select方法处时,会进行阻塞等待。

    那为什么NIO的核心组件selector 会使用一个阻塞性方法呢 ?

    这是由于NIO本来指的就是New IO,只是在网络数据处理时,是非阻塞的,不需要单独存在一个线程管理socket等待对方写入。
    而NIO更多强调的是多路复用,即通过一个线程(进程),即可以管理多个网络连接(所谓的多路)的通信。

     

  • 相关阅读:
    centos安装mysql8.0.20、tar包安装方式
    零代码编程:用ChatGPT批量转换多个视频文件夹到音频并自动移动文件夹
    8月算法训练------第十四天(数学)解题报告
    【C语言】详解计算机二级c语言程序题
    Java OA系统日程管理模块
    <老式喜剧>
    音乐制作软件 Ableton Live 11 Suite mac中文版功能介绍
    Dubbo详解,用心看这一篇文章就够了【重点】
    spring-boot集成mybatis真的很简单吗?
    中国汉服行业发展深度调研与未来趋势预测报告
  • 原文地址:https://www.cnblogs.com/jilodream/p/17420838.html