zoukankan      html  css  js  c++  java
  • java非阻塞NIO和阻塞IO

    1         非阻塞NIO和阻塞IO

    1.1           定义

    阻塞IO:线程被阻塞,去处理一个读取和写入,中间如果有等待时间,则线程被占用,也不能处理其他任务;

    非阻塞IO(new IO):引入了通道的概念,一个连接对应一个通道,为每个通道配置缓冲区,线程去轮询查看的通道的状态,如果某个通道上有数据准备好了,则通道状态会发生改变,线程就去处理这个读写操作;

    1.2           非阻塞实现的核心Selector

    Selector 一般称为选择器 ,或者多路复用器。在通道上建立socket连接,然后调用channel.register(selector, Selectionkey.OP_READ);将通道注册到Selector上,并且申明这个连接是读(READ)、写(WRITE)、接受(ACCEPT)、连接(     CONNECT),或者四个功能都包括;这样就可以用一个线程,一个Selector去管理多个连接;selector会去循环查询各个通道的状态,如果通道状态发生改变,会去判断通道的类型(读、写、接受、连接),然后再对应去做处理;

    (1)Selector的创建

    通过调用Selector.open()方法创建一个Selector对象,如下:

    Selector selector = Selector.open();

    (2)注册Channel到Selector

    channel.configureBlocking(false);

    SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

    第二个参数是通道的类型,有四个值,方便位运算;

    读 : SelectionKey.OP_READ ( 1)

    写 : SelectionKey.OP_WRITE ( 4)

    连接 : SelectionKey.OP_CONNECT ( 8)

    接收 : SelectionKey.OP_ACCEPT ( 16)

    若注册时不止监听一个事件,则可以使用“位或”操作符连接。

    1.3           SelectableChannle通道上建立连接

    (1)非阻塞通道

    AbstractSelectableChannel抽象类继承了SelectableChannle接口, SocketChannel、ServerSocketChannel、DatagramChannel都是直接继承了 AbstractSelectableChannel抽象类 。SocketChannel是用于socket客户端,ServerSocketChannel是用于socket服务器;

    (2)阻塞通道

    FileChannel还是不能实现非阻塞,对文件的读写IO,不能同时写入一个文件。

    1.4           SelectionKey通道状态

    SelectionKey 代表各个通道的状态信号,通过调用选择器的函数来获取到所有有信号的通道的状态,Set<SelectionKey> keys = selector.selectedKeys();然后在通过SelectionKey的方法去判断是哪种信号。

    (1)key.attachment(); //返回SelectionKey的attachment,attachment可以在注册channel的时候指定。

    (2)key.channel(); // 返回该SelectionKey对应的channel。

    (3)key.selector(); // 返回该SelectionKey对应的Selector。

    (4)key.interestOps(); //返回代表需要Selector监控的IO操作的bit mask

    我们可以通过与操作来判断Selector是否对Channel的某种事件感兴趣,感兴趣的boolean为1;

    int interestSet = selectionKey.interestOps();

    boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;

    boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;

    boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;

    boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

    (5)key.readyOps(); // 返回一个bit mask,代表在相应channel上可以进行的IO操作。可以通过与运算去判断是否准备好;

    (6)直接调用SelectionKey对象的函数判断信号状态

    boolean isReadable() 检测 Channal 中读事件是否就绪

    boolean isWritable() 检测 Channal 中写事件是否就绪

    boolean isConnectable() 检测 Channel 中连接是否就绪

    boolean isAcceptable() 检测 Channel 中接收是否就绪

    1.5           阻塞IO使用实例

    客户端向服务器端发送一张图片
    package com.happybks.nio.nio;
     
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.nio.file.Paths;
    import java.nio.file.StandardOpenOption;
     
    import org.junit.Test;
     
    public class TestBlockingNIO {
     
             //客户端
             @Test
             public void client() throws IOException{
                             //1、获取通道(open这种方法是jdk1.7之后才引入的)
                             SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
                             
                             //2、分配指定大小的缓冲区
                             ByteBuffer buf=ByteBuffer.allocate(1024);
                             
                             //3、从本地读取文件,并发送到服务端
                             FileChannel inFileChannel=FileChannel.open(Paths.get("D:/Test/NIO/1.jpg"), StandardOpenOption.READ);
                             while(inFileChannel.read(buf)!=-1){
                                            buf.flip();
                                            socketChannel.write(buf);
                                            buf.clear();
                             }
                             
                             //在阻塞IO下,如果关闭socketChannel,那么服务端不知道客户端是否已经把所有数据发完,所以会一直阻塞。
                             socketChannel.shutdownOutput();
                             //另一种方法就是把这个线程切换成非阻塞模式
                             
                             
                             //接收服务端反馈
                             int len = 0;
                             while((len = socketChannel.read(buf)) !=-1){
                                            buf.flip();
                                            System.out.println(new String(buf.array(),0,len));
                             }
                             
                             //4、关闭通道
                             inFileChannel.close();
                             socketChannel.close();
             }
             
             //服务端
             @Test
             public void server() throws IOException{
                             //1、获取端口
                             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
             
                             //2、绑定连接
                             serverSocketChannel.bind(new InetSocketAddress(8888));
                             
                             //3、获取客户端连接的通道
                             SocketChannel socketChannel = serverSocketChannel.accept();
                             
                             //4、接收客户端的数据,保存到本地。(提到本地,就要弄个FileChannel)
                             FileChannel outFileChannel = FileChannel.open(Paths.get("D:/Test/NIO/2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
                             ByteBuffer buf=ByteBuffer.allocate(1024);
                             while(socketChannel.read(buf)!=-1){
                                            buf.flip();
                                            outFileChannel.write(buf);
                                            buf.clear();
                             }
                             
                             //发送反馈给客户端
                             buf.put("服务端接收数据成功!".getBytes());
                             buf.flip();
                             socketChannel.write(buf);
                             
                             socketChannel.close();
                             outFileChannel.close();
                             serverSocketChannel.close();
                             
             }
    }

    1.6           非阻塞IO使用实例

    package com.happybks.nio.nio;

    import java.io.IOException;

    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.Date;

    import java.util.Iterator;

    import org.junit.Test;

    public class TestNonBlockingNIO {

           //客户端

           @Test

           public  void client() throws IOException{

                  //1、获取通道

                  SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));

                  //2、切换非阻塞模式

                  socketChannel.configureBlocking(false);

                 

                  //3、分配指定大小的缓冲区

                  ByteBuffer buf=ByteBuffer.allocate(1024);

                 

                  //4、发送数据服务器

                  buf.put(new Date().toString().getBytes());

                  buf.flip();

                  socketChannel.write(buf);

                  buf.clear();

                 

                  //5、关闭通道

                  socketChannel.close();

           }

          

           //服务端

           @Test

           public  void server() throws IOException{

                  //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();

                                      

                                       //11、客户端连接socketChannel也需要切换非阻塞模式

                                       socketChannel.configureBlocking(false);

                                      

                                       //12、将该通道注册到选择器上,监控客户端socketChannel的读就绪事件

                                       socketChannel.register(selector, SelectionKey.OP_READ);

                                }

                                else if(sk.isReadable()){

                                       //13、获取当前选择器上“读就绪”状态的通道

                                       SocketChannel socketChannel = (SocketChannel) sk.channel();

                                      

                                       //14、读取数据

                                       ByteBuffer buf=ByteBuffer.allocate(1024);

                                      

                                       int len=0;

                                       while((len=socketChannel.read(buf))>0){

                                              buf.flip();

                                              System.out.println(new String(buf.array(),0,len));

                                              buf.clear();

                                       }

                                }

                               

                                //15、取消选择键SelectionKey

                                iterator.remove();

                         }

                  }

           }

    }

    自己开发了一个股票智能分析软件,功能很强大,需要的点击下面的链接获取:

    https://www.cnblogs.com/bclshuai/p/11380657.html

    百度云盘下载地址:

    链接:https://pan.baidu.com/s/1swkQzCIKI3g3ObcebgpIDg

    提取码:mc8l

    微信公众号获取最新的软件和视频介绍

    QStockView

  • 相关阅读:
    RAID 0 软件实现(Windows 系统)
    keepalived 实现LVS负载均衡高可用集群(一)
    ISCSI服务端-客户端基础配置
    LVS负载分担(DR模式)基础搭建(一)
    LVS负载分担(NAT模式)基础搭建
    Chronyd同步时间(Server/Client)配置
    代码签名
    paillier加密算法原理详解
    pip安装了包但pycharm里找不到(pip如何安装到conda下)
    苹果系统iOS、macOS应用管理机制
  • 原文地址:https://www.cnblogs.com/bclshuai/p/10734348.html
Copyright © 2011-2022 走看看