Java I/O

       部分内容参考:https://blog.csdn.net/yjp198713/column/info/18912

1. 概述

1.1 I/O 是什么

       输入/输出(I/O)是在主存和外部设备(例如磁盘驱动器、終端和网络)之间复制数据的过程。

       输入操作是从I/O设备复制数据到主存,而输出操作是从主存复制数据到I/O设备。

1.2 I/O 对性能的影响

       在我们的程序中,通常涉及到大量的I/O操作,如一个Http请求、数据库的CRUD、文件的上传与下载等。

       但是用户进程并不能直接控制主存将数据在外部设备之间的复制,如用户程序想要对磁盘中操作系统的内容进行修改,这种情况显然是不被允许的,所以这就需要操作系统的介入了。

在这里插入图片描述
       首先,我们可以看一下用户进行一次I\O操作的流程。应用进程发出读请求,由用户态切入内核态进行系统调用,接着等待磁盘的数据拷贝到内核态,然后由内核态复制到用户态空间,然后再切换回用户态。当用户对用户态空间的数据查看并修改后,发出写请求,由用户态切入内核态进行系统调用,将用户态的空间拷贝到内核态,最后在拷贝到磁盘,结束后切换回用户态。

       可以看到,完成一次读写操作,涉及到四次用户态与内核态的转换,同时需要经历四次的拷贝。这一切操作都非常影响性能,所以I\O操作对性能的影响非常严重,由此产生了多种I\O模型来提升性能。

1.3 同步与异步

       同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)

       所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。

       而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

       举个通俗的例子:你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

1.4 阻塞与非阻塞

       阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

       阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

       非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

       还是上面的例子你:打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

2. BIO (Blocking I/O)

       BIO即为我们传统意义上的I/O,阻塞式I/O

       BIO模型示意图:

在这里插入图片描述

       通过上图可以看到,应用进程发出一个系统调用后,便一直等待数据到达,其间一直没有做任何事。

2.1 传统 BIO 示例

       这里,我们通过代码,体验一下这种情况:

package BIO;
import	java.util.Scanner;
import java.io.IOException;
import	java.net.ServerSocket;
import java.net.Socket;

/**
 * @author wangzhao
 * @date 2020/6/19 16:34
 */
public class BIOServer {

    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);
        while (true){
            System.out.println("等待客户端连接!!!");
            Socket socket = ss.accept();
            System.out.println(socket.getRemoteSocketAddress() + "连接了服务器!!!");
            Scanner scanner = new Scanner(socket.getInputStream());
            System.out.println("等待客户端输入!!!");
            String msg = scanner.nextLine();
            System.out.println(msg);
        }
    }
}
package BIO;
import java.io.IOException;
import java.io.OutputStream;
import	java.net.Socket;
import java.util.Scanner;

/**
 * @author wangzhao
 * @date 2020/6/19 16:51
 */
public class BIOClient {

    public static void main(String[] args) throws IOException {
    	System.out.println("客户端启动了!");
        Socket socket = new Socket("127.0.0.1", 8888);
        OutputStream outputStream = socket.getOutputStream();
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        outputStream.write(new String(s).getBytes());
        socket.close();
    }
}

       1. 启动服务器端

在这里插入图片描述
       2. 启动一个客户端程序
在这里插入图片描述

在这里插入图片描述

       3. 在启动一个客户端程序(此时有两个客户端程序在运行)

在这里插入图片描述
在这里插入图片描述
       4. 第一个客户端进行输入

在这里插入图片描述
在这里插入图片描述

       观察上述的代码,可以发现,服务端在两处被阻塞,在阻塞其间无法处理其他客户端的连接,这样的话就再阻塞期间无法处理其他的事件,效率非常低下。

2.2 伪异步 BIO 示例

       我们可以为每一个Socket开启一个线程,这样阻塞的话,也只会让其中的一个线程被阻塞,不会影响其他线程的Socket通信。

package BIO;
import	java.util.Scanner;
import java.io.IOException;
import	java.net.ServerSocket;
import java.net.Socket;

/**
 * @author wangzhao
 * @date 2020/6/19 16:34
 */
public class BIOServer {

    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);
        while (true){
            System.out.println("等待客户端连接!!!");
            Socket socket = ss.accept();
            System.out.println(socket.getRemoteSocketAddress() + "连接了服务器!!!");

            new Thread(() -> {
                Scanner scanner = null;
                try {
                    scanner = new Scanner(socket.getInputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println("等待客户端输入!!!");
                String msg = scanner.nextLine();
                System.out.println(msg);
            }).start();

        }
    }
}

在这里插入图片描述在这里插入图片描述在这里插入图片描述

       为每一个Socket都创建一个线程,线程的频繁创建和销毁非常浪费资源,所以可以使用线程池替换线程的创建。

       不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。

2.3 阻塞的问题根源

       为什么accept()read()方法会被阻塞。API文档中对于 serverSocket.accept() 方法的使用描述:

Listens for a connection to be made to this socket and accepts it. 
The method blocks until a connection is made.

       服务器线程发起一个accept动作,询问操作系统 是否有新的socket套接字信息从端口xx发送过来。

       注意,是询问操作系统。也就是说socket套接字的IO模式支持是基于操作系统的,那么自然同步IO/异步IO的支持就是需要操作系统级别的了。如下图:

在这里插入图片描述
       如果操作系统没有发现有套接字从指定的端口xx来,那么操作系统就会等待。这样serverSocket.accept()方法就会一直等待。这就是为什么accept()方法为什么会阻塞:它内部的实现是使用的操作系统级别的同步IO

3. NIO (No Blocking I/O)

       NIO非阻塞式I/O

       NIO模型示意图:

在这里插入图片描述
       通过模型示意图可以看到,NIO的应用进程每隔一段时间询问内核数据有无被准备好,没有的话就可以执行自己的事情。虽然这段时间可能在我们看来非常短暂,但是CPU的运行速度非常快,完全可以执行大量的运算。

3.1 Java NIO 简介

       Java NIO模型图如下:

在这里插入图片描述

Channel

       通道,被建立的一个应用程序和操作系统交互事件、传递内容的渠道(注意是连接到操作系统)。一个通道会有一个专属的文件状态描述符。那么既然是和操作系统进行内容的传递,那么说明应用程序可以通过通道读取数据,也可以通过通道向操作系统写数据。

       JDK API中的Channel的描述是:

通道表示到外部设备(如硬件设备、文件、网络套接字或程序组件)的开放连接,该实体能够执行一个或多个不同的I/O操作,例如读取或写入。

道通道是打开的还是关闭的?一个通道在创建时是打开的,一旦关闭它就保持关闭状态。
一旦通道关闭,任何对其调用I/O操作的尝试都将导致引发ClosedChannelException。
通道是否打开可以通过调用其 isOpen 方法进行测试。

       JAVA NIO框架中,Channel通道的类型有:

在这里插入图片描述

  1. 所有被Selector(选择器)注册的通道,只能是继承SelectableChannel类的子类。
  2. ServerSocketChannel:应用服务器程序的监听通道。只有通过这个通道,应用程序才能向操作系统注册支持“多路复用IO”的端口监听。同时支持UDP协议和TCP协议。
  3. ScoketChannel:TCP Socket套接字的监听通道,一个Socket套接字对应了一个客户端IP:端口服务器IP:端口的通信连接。
  4. DatagramChannel:UDP 数据报文的监听通道。

Buffer

       数据缓存区:在JAVA NIO框架中,为了保证每个通道的数据读写速度。JAVA NIO框架为每一种需要支持数据读写的通道集成了Buffer的支持。

       这句话怎么理解呢?例如ServerSocketChannel通道它只支持对OP_ACCEPT事件的监听,所以它是不能直接进行网络数据内容的读写的。所以ServerSocketChannel是没有集成Buffer的。

       Buffer有两种工作模式:写模式和读模式。在读模式下,应用程序只能从Buffer中读取数据,不能进行写操作。但是在写模式下,应用程序是可以进行读操作的,这就表示可能会出现脏读的情况。所以一旦您决定要从Buffer中读取数据,一定要将Buffer的状态改为读模式。

       Buffer中的参数项:

在这里插入图片描述
       在进行读写时,变化如下:

  1. 我们通过 ByteBuffer.allocate(11) 方法创建一个 11byte 的数组缓冲区,初始状态如图所示,position 的位置为 0capacitylimit 默认都是数组长度。
    在这里插入图片描述
  2. 当我们写入 5 个字节时位置变化如下图所示:

在这里插入图片描述

  1. 这时我们需要将缓冲区的 5 个字节数据写入 Channel 通信信道,所以我们需要调用 byteBuffer.flip() 方法,数组的状态又发生如下变化:

在这里插入图片描述
       这时底层操作系统就可以从缓冲区中正确读取这 5 个字节数据发送出去了。在下一次写数据之前我们在调一下 clear() 方法。缓冲区的索引状态又回到初始位置。

       这里还要说明一下 mark,当我们调用 mark() 时,它将记录当前 position 的前一个位置,当我们调用 reset 时,position 将恢复 mark 记录下来的值。

       还有一点需要说明,通过 Channel 获取的 I/O 数据首先要经过操作系统的 Socket 缓冲区再将数据复制到 Buffer 中,这个的操作系统缓冲区就是底层的 TCP 协议关联的 RecvQ 或者 SendQ 队列,从操作系统缓冲区到用户缓冲区复制数据比较耗性能,Buffer 提供了另外一种直接操作操作系统缓冲区的的方式即 ByteBuffer.allocateDirector(size),这个方法返回的 byteBuffer 就是与底层存储空间关联的缓冲区,它的操作方式与 linux2.4 内核的 sendfile 操作方式类似。

Selector

       Selector的英文含义是“选择器”,也可以把它称之为“轮询代理器”、“事件订阅器”、“Channel容器管理机”都行。

       事件订阅和Channel管理:

       应用程序将向Selector对象注册需要它关注的Channel,以及具体的某一个Channel会对哪些IO事件感兴趣。Selector中也会维护一个“已经注册的Channel”的容器。

       以下代码来自WindowsSelectorImpl实现类中,对已经注册的Channel的管理容器:

   // Initial capacity of the poll array
   private final int INIT_CAP = 8;
   // Maximum number of sockets for select().
   // Should be INIT_CAP times a power of 2
   private final static int MAX_SELECTABLE_FDS = 1024;

   // The list of SelectableChannels serviced by this Selector. Every mod
   // MAX_SELECTABLE_FDS entry is bogus, to align this array with the poll
   // array,  where the corresponding entry is occupied by the wakeupSocket
   private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[INIT_CAP];

       轮询代理: 应用层不再通过阻塞模式或者非阻塞模式直接询问操作系统“事件有没有发生”,而是由Selector代其询问。

3.2 NIO 示例

package NIO;
import java.nio.channels.*;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * @author wangzhao
 * @date 2020/6/19 19:54
 */
public class NIOServer {

    public static void main(String arg[]) {

        System.out.println("服务器端启动");
        try {
            //1、创建服务器端通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //2、切换异步非阻塞
            serverSocketChannel.configureBlocking(false);
            //3、切换读取模式
            serverSocketChannel.bind(new InetSocketAddress(8888));
            //4、获取选择器
            Selector selector = Selector.open();
            //5、将通道注册到选择器中去,并且监听已经收到的事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //6、轮询获取“已经准备就绪的”的事件
            while (selector.select() > 0) {
                //7、获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    //8、获取准备“就绪”的是事件
                    SelectionKey sk = iterator.next();
                    //9、 判断具体是什么事件准备就绪
                    if (sk.isAcceptable()) {
                        //10、 若“接收就绪”,获取客户端连接
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        System.out.println(socketChannel.getRemoteAddress() + "连接了!!!");
                        //11、 切换非阻塞模式
                        socketChannel.configureBlocking(false);
                        //12、 将该通道注册到选择器上
                        socketChannel.register(selector, SelectionKey.OP_READ);


                    } else if (sk.isReadable()){
                        //13、 获取当前选择器上“读就绪”状态的通道
                        SocketChannel sChannel = (SocketChannel) sk.channel();

                        //14、 指定缓冲区大小,读取数据
                        ByteBuffer buf = ByteBuffer.allocate(1024);

                        int len = 0;
                        while ((len = sChannel.read(buf)) > 0) {  //sChannel 读取数据到ByteBuffer
                            buf.flip(); //切换到读取模式
                            String str = new String(buf.array(), 0, len);  //读取
                            System.out.println("msg = "+str);
                            buf.clear();
                        }

                    }
                    //15. 取消选择键 SelectionKey
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package NIO;
import java.io.IOException;
import	java.util.Scanner;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @author wangzhao
 * @date 2020/6/19 20:16
 */
public class NIOClient {

    public static void main(String[] args) {
        System.out.println("客户端已经启动.............");
        try {
            //1、创建socker通道
            SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
            //2、切换异步非阻塞
            socketChannel.configureBlocking(false);
            //3、指定缓冲区大小
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            Scanner scanner = new Scanner(System.in);
            byteBuffer.put(scanner.nextLine().getBytes());
            //4、切换到读取模式
            byteBuffer.flip();
            //5、写入到缓冲区
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
            //6、关闭通道
            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

  1. 首先我们启动服务端

在这里插入图片描述

  1. 接着运行一个客户端

在这里插入图片描述
在这里插入图片描述

  1. 再运行一个客户端

在这里插入图片描述在这里插入图片描述
4. 客户端进行输入

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

       通过对比NIO Server和伪异步BIO的代码,我们实现了单线程便与之等同的功能。

3.3 I/O 多路复用模型

       NIO中单线程的环境下并不会发生阻塞,这是因为其采用了I/O多路复用的模型。

       在多路复用IO模型中,会有一个线程(Java中的Selector)不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。

在这里插入图片描述

       I/O 多路复用实现原理

4. AIO (Async IO)

       异步IO则采用“订阅-通知”模式:即应用程序向操作系统注册IO监听,然后继续做自己的事情。当操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数。

在这里插入图片描述

  1. 和同步IO一样,异步IO也是由操作系统进行支持的。微软的windows系统提供了一种异步IO技术:IOCPI/O CompletionPortI/O完成端口);
  2. Linux下由于没有这种异步IO技术,所以使用的是epoll对异步IO进行模拟。

4.1 Java AIO 简介

4.2 AIO 示例

package demo.com.test.io.aio;

   import java.net.InetSocketAddress;
   import java.nio.channels.AsynchronousChannelGroup;
   import java.nio.channels.AsynchronousServerSocketChannel;
   import java.util.concurrent.ExecutorService;
   import java.util.concurrent.Executors;

   public class AioSocketServer {


       private static final Object waitObject = new Object();

       /**
        * @param args
        * @throws Exception
        */
       public static void main(String[] args) throws Exception {
           /*
            * 对于使用的线程池技术,我一定要多说几句
            * 1、Executors是线程池生成工具,通过这个工具我们可以很轻松的生成“固定大小的线程池”、“调度池”、“可伸缩线程数量的池”。具体请看API Doc
            * 2、当然您也可以通过ThreadPoolExecutor直接生成池。
            * 3、这个线程池是用来得到操作系统的“IO事件通知”的,不是用来进行“得到IO数据后的业务处理的”。要进行后者的操作,您可以再使用一个池(最好不要混用)
            * 4、您也可以不使用线程池(不推荐),如果决定不使用线程池,直接AsynchronousServerSocketChannel.open()就行了。
            * */
           ExecutorService threadPool = Executors.newFixedThreadPool(20);
           AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(threadPool);
           final AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open(group);

           //设置要监听的端口“0.0.0.0”代表本机所有IP设备
           serverSocket.bind(new InetSocketAddress("0.0.0.0", 8083));
           //为AsynchronousServerSocketChannel注册监听,注意只是为AsynchronousServerSocketChannel通道注册监听
           //并不包括为 随后客户端和服务器 socketchannel通道注册的监听
           serverSocket.accept(null, new ServerSocketChannelHandle(serverSocket));

           //等待,以便观察现象(这个和要讲解的原理本身没有任何关系,只是为了保证守护线程不会退出)
           synchronized(waitObject) {
               waitObject.wait();
           }
       }
   }

   package demo.com.test.io.aio;

   import java.nio.ByteBuffer;
   import java.nio.channels.AsynchronousServerSocketChannel;
   import java.nio.channels.AsynchronousSocketChannel;
   import java.nio.channels.CompletionHandler;

   import org.apache.commons.logging.Log;
   import org.apache.commons.logging.LogFactory;

   /**
    * 这个处理器类,专门用来响应 ServerSocketChannel 的事件。
    * ServerSocketChannel只有一种事件:接受客户端的连接
    * @author keep_trying
    */
   public class ServerSocketChannelHandle implements CompletionHandler<AsynchronousSocketChannel, Void> {
       /**
        * 日志
        */
       private static final Log LOGGER = LogFactory.getLog(ServerSocketChannelHandle.class);

       private AsynchronousServerSocketChannel serverSocketChannel;

       /**
        * @param serverSocketChannel
        */
       public ServerSocketChannelHandle(AsynchronousServerSocketChannel serverSocketChannel) {
           this.serverSocketChannel = serverSocketChannel;
       }

       /**
        * 注意,我们分别观察 this、socketChannel、attachment三个对象的id。
        * 来观察不同客户端连接到达时,这三个对象的变化,以说明ServerSocketChannelHandle的监听模式
        */
       @Override
       public void completed(AsynchronousSocketChannel socketChannel, Void attachment) {
           ServerSocketChannelHandle.LOGGER.info("completed(AsynchronousSocketChannel result, ByteBuffer attachment)");
           //每次都要重新注册监听(一次注册,一次响应),但是由于“文件状态标示符”是独享的,所以不需要担心有“漏掉的”事件
           this.serverSocketChannel.accept(attachment, this);

           //为这个新的socketChannel注册“read”事件,以便操作系统在收到数据并准备好后,主动通知应用程序
           //在这里,由于我们要将这个客户端多次传输的数据累加起来一起处理,所以我们将一个stringbuffer对象作为一个“附件”依附在这个channel上
           //
           ByteBuffer readBuffer = ByteBuffer.allocate(2550);
           socketChannel.read(readBuffer, new StringBuffer(), new SocketChannelReadHandle(socketChannel , readBuffer));
       }

       /* (non-Javadoc)
        * @see java.nio.channels.CompletionHandler#failed(java.lang.Throwable, java.lang.Object)
        */
       @Override
       public void failed(Throwable exc, Void attachment) {
           ServerSocketChannelHandle.LOGGER.info("failed(Throwable exc, ByteBuffer attachment)");
       }
   }

   package demo.com.test.io.aio;

   import java.io.IOException;
   import java.io.UnsupportedEncodingException;
   import java.net.URLEncoder;
   import java.nio.ByteBuffer;
   import java.nio.channels.AsynchronousSocketChannel;
   import java.nio.channels.CompletionHandler;

   import org.apache.commons.logging.Log;
   import org.apache.commons.logging.LogFactory;

   /**
    * 负责对每一个socketChannel的数据获取事件进行监听。<p>
    *
    * 重要的说明:一个socketchannel都会有一个独立工作的SocketChannelReadHandle对象(CompletionHandler接口的实现),
    * 其中又都将独享一个“文件状态标示”对象FileDescriptor、
    * 一个独立的由程序员定义的Buffer缓存(这里我们使用的是ByteBuffer)、
    * 所以不用担心在服务器端会出现“窜对象”这种情况,因为JAVA AIO框架已经帮您组织好了。<p>
    *
    * 但是最重要的,用于生成channel的对象:AsynchronousChannelProvider是单例模式,无论在哪组socketchannel,
    * 对是一个对象引用(但这没关系,因为您不会直接操作这个AsynchronousChannelProvider对象)。
    * @author keep_trying
    */
   public class SocketChannelReadHandle implements CompletionHandler<Integer, StringBuffer> {
       /**
        * 日志
        */
       private static final Log LOGGER = LogFactory.getLog(SocketChannelReadHandle.class);

       private AsynchronousSocketChannel socketChannel;

       /**
        * 专门用于进行这个通道数据缓存操作的ByteBuffer<br>
        * 当然,您也可以作为CompletionHandler的attachment形式传入。<br>
        * 这是,在这段示例代码中,attachment被我们用来记录所有传送过来的Stringbuffer了。
        */
       private ByteBuffer byteBuffer;

       public SocketChannelReadHandle(AsynchronousSocketChannel socketChannel , ByteBuffer byteBuffer) {
           this.socketChannel = socketChannel;
           this.byteBuffer = byteBuffer;
       }

       /* (non-Javadoc)
        * @see java.nio.channels.CompletionHandler#completed(java.lang.Object, java.lang.Object)
        */
       @Override
       public void completed(Integer result, StringBuffer historyContext) {
           //如果条件成立,说明客户端主动终止了TCP套接字,这时服务端终止就可以了
           if(result == -1) {
               try {
                   this.socketChannel.close();
               } catch (IOException e) {
                   SocketChannelReadHandle.LOGGER.error(e);
               }
               return;
           }

           SocketChannelReadHandle.LOGGER.info("completed(Integer result, Void attachment) : 然后我们来取出通道中准备好的值");
           /*
            * 实际上,由于我们从Integer result知道了本次channel从操作系统获取数据总长度
            * 所以实际上,我们不需要切换成“读模式”的,但是为了保证编码的规范性,还是建议进行切换。
            *
            * 另外,无论是JAVA AIO框架还是JAVA NIO框架,都会出现“buffer的总容量”小于“当前从操作系统获取到的总数据量”,
            * 但区别是,JAVA AIO框架中,我们不需要专门考虑处理这样的情况,因为JAVA AIO框架已经帮我们做了处理(做成了多次通知)
            * */
           this.byteBuffer.flip();
           byte[] contexts = new byte[1024];
           this.byteBuffer.get(contexts, 0, result);
           this.byteBuffer.clear();
           try {
               String nowContent = new String(contexts , 0 , result , "UTF-8");
               historyContext.append(nowContent);
               SocketChannelReadHandle.LOGGER.info("================目前的传输结果:" + historyContext);
           } catch (UnsupportedEncodingException e) {
               SocketChannelReadHandle.LOGGER.error(e);
           }

           //如果条件成立,说明还没有接收到“结束标记”
           if(historyContext.indexOf("over") == -1) {
               return;
           }else{
               //清空已经读取的缓存,并从新切换为写状态(这里要注意clear()和capacity()两个方法的区别)
               this.byteBuffer.clear();
               SocketChannelReadHandle.LOGGER.info("客户端发来的信息======message : " + historyContext);

               //======================================================
               //          当然接受完成后,可以在这里正式处理业务了        
               //======================================================

               //回发数据,并关闭channel
               ByteBuffer sendBuffer = null;
               try {
                   sendBuffer = ByteBuffer.wrap(URLEncoder.encode("你好客户端,这是服务器的返回数据", "UTF-8").getBytes());
                   socketChannel.write(sendBuffer);
                   socketChannel.close();
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }

           //=========================================================================
           //          和上篇文章的代码相同,我们以“over”符号作为客户端完整信息的标记
           //=========================================================================
           SocketChannelReadHandle.LOGGER.info("=======收到完整信息,开始处理业务=========");
           historyContext = new StringBuffer();

           //还要继续监听(一次监听一次通知)
           this.socketChannel.read(this.byteBuffer, historyContext, this);
       }

       /* (non-Javadoc)
        * @see java.nio.channels.CompletionHandler#failed(java.lang.Throwable, java.lang.Object)
        */
       @Override
       public void failed(Throwable exc, StringBuffer historyContext) {
           SocketChannelReadHandle.LOGGER.info("=====发现客户端异常关闭,服务器将关闭TCP通道");
           try {
               this.socketChannel.close();
           } catch (IOException e) {
               SocketChannelReadHandle.LOGGER.error(e);
           }
       }
   }

       在JAVA NIO框架中,我们说到了一个重要概念“selector”(选择器)。它负责代替应用查询所有已注册的通道到操作系统中进行IO事件轮询、管理当前注册的通道集合,定位发生事件的通道等操作;
       但是在JAVA AIO框架中,由于应用程序不是“轮询”方式,而是订阅-通知方式,所以不再需要“selector”(选择器)了,改由channel通道直接到操作系统注册监听。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值