day25【MappedByteBuffer、网络编程、Selector选择器、NIO2-AIO(异步、非阻塞)】课上

1.使用MappedByteBuffer复制超过2G的文件(理解)

1.图解

在这里插入图片描述

2.代码演示

package com.itheima.sh.filechannel_01;

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/*
    将D:\上课视频.zip复制到F:\上课视频.zip
 */
public class FileChannelDemo01 {
    public static void main(String[] args) throws Exception{
        //1.创建访问随机文件的类的对象
        RandomAccessFile f1 = new RandomAccessFile("D:\\上课视频.zip", "r");//只读
        RandomAccessFile f2 = new RandomAccessFile("F:\\上课视频.zip", "rw");//读写

        //2.获取通道
        FileChannel c1 = f1.getChannel();
        FileChannel c2 = f2.getChannel();

        //3.获取文件大小
        long size = c1.size();
//        System.out.println("size = " + size);//2744459212
        //4.定义变量保存每次复制的文件的大小
        long everySize = 1024 * 1024 * 500;//500M  1024字节等于1KB   1024 * 1024  等于1M

        //5.定义变量保存复制文件的次数
        long count = (size % everySize == 0) ? size / everySize : size / everySize + 1;
        //6.使用循环控制每次复制的代码
        for (long i = 0; i < count; i++) {//i等于0表示第一次  1 表示第二次
            //7.定义变量保存每次开始复制的起始索引
            long start = everySize * i;
            //8.定义变量保存每次复制的文件的真正大小
            long trueSize = (size - start > everySize) ? everySize : size - start;
            /*
                abstract  MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
                    参数:
                        mode:表示读写模式
                        position:表示复制文件的起始位置
                        size:表示每次文件的大小
             */
            //9.创建缓冲区
            MappedByteBuffer m1 = c1.map(FileChannel.MapMode.READ_ONLY, start, trueSize);//只读
            MappedByteBuffer m2 = c2.map(FileChannel.MapMode.READ_WRITE, start, trueSize);//读写
            //10.读写数据
            for (long l = 0; l < trueSize; l++) {
                //获取
                byte b = m1.get();
                //存储
                m2.put(b);
            }
        }
        //释放资源
        c2.close();
        c1.close();
        f2.close();
        f1.close();

    }
}

2.网络编程收发信息 (掌握)

1.客户端

package com.itheima.sh.net_channel_02;

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

/*
    nio的客户端:
        1.在nio中使用SocketChannel表示客户端套接字的通道
        2.获取SocketChannel对象方法:
            1)简单方式:
                使用SocketChannel类中的静态方法:
                    static SocketChannel open(SocketAddress remote)  参数:remote属于SocketAddress类型,属于抽象类,我们使用子类创建对象
                                            InetSocketAddress 类,构造方法:InetSocketAddress(String hostname, int port)
                                                                                参数:
                                                                                    hostname:表示连接的服务器ip地址
                                                                                    port:表示连接的服务器的端口号
          2)麻烦方式:
             使用SocketChannel类中的静态方法:
                 static SocketChannel open();
              使用SocketChannel类中的非静态方法:
              boolean connect(SocketAddress remote)
                             参数:remote属于SocketAddress类型,属于抽象类,我们使用子类创建对象
                                            InetSocketAddress 类,构造方法:InetSocketAddress(String hostname, int port)
                                                                                参数:
                                                                                    hostname:表示连接的服务器ip地址
                                                                                    port:表示连接的服务器的端口号
      3.写方法:
          int write(ByteBuffer src) 将字节序列从给定的缓冲区中写入此通道
          abstract  int read(ByteBuffer dst) 将字节序列从此通道中读入给定的缓冲区。
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建客户端通道对象连接服务器  static SocketChannel open(SocketAddress remote)
        //InetSocketAddress(String hostname, int port)
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
        //2.向服务器写数据
        //2.1创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //2.2向缓冲区中添加数据
        buffer.put("hello,我来了".getBytes());
        //2.3切换读模式 limit拿到position位置 position拿到0位置 清除mark
        buffer.flip();
        // int write(ByteBuffer src) 将字节序列从给定的缓冲区中写入此通道
        sc.write(buffer);
        //关闭流
        sc.close();
    }
}

小结:

1.创建客户端对象:

使用SocketChannel类中的静态方法:
   static SocketChannel open(SocketAddress remote)  参数:remote属于SocketAddress类型,属于抽象类,我们使用子类创建对象
         InetSocketAddress 类,构造方法:InetSocketAddress(String hostname, int port)
                参数:
    				hostname:表示连接的服务器ip地址
                     port:表示连接的服务器的端口号
  1. int write(ByteBuffer src) 将字节序列从给定的缓冲区中写入此通道
  2. abstract int read(ByteBuffer dst) 将字节序列从此通道中读入给定的缓冲区。

2.服务器端

package com.itheima.sh.net_channel_02;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/*
    服务器端:
    1.在nio中使用ServerSocketChannel表示服务器端套接字的通道
    2.创建ServerSocketChannel对象:
        1)使用ServerSocketChannel类的方法:static ServerSocketChannel open()
        2)使用ServerSocketChannel对象调用父类的绑定端口号方法:
            ServerSocketChannel bind(SocketAddress local) 将通道的套接字绑定到本地地址,并配置套接字以
                    参数:SocketAddress属于抽象类,使用子类InetSocketAddress(int port) 指定服务器的端口号
   3.使用服务器的对象调用方法获取客户端通道对象:
    abstract  SocketChannel accept()  接受到此通道套接字的连接。
   4.使用侦听的客户端对象调用客户端中的读取方法:
     int read(ByteBuffer dst)
 */
public class ServerDemo02 {
    public static void main(String[] args) throws IOException {
        //1.创建ServerSocketChannel对象
        //1.1 使用ServerSocketChannel类的方法:static ServerSocketChannel open()
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //1.2 ServerSocketChannel bind(SocketAddress local)
        //使用子类InetSocketAddress(int port) 指定服务器的端口号
        ssc.bind(new InetSocketAddress(9999));
        //2.使用服务器的对象调用方法获取客户端通道对象:abstract  SocketChannel accept()  接受到此通道套接字的连接。
        System.out.println("1111");
        //阻塞到这里了,等待客户端访问。
        SocketChannel sc = ssc.accept();
        System.out.println("2222");
        //3.读取数据 int read(ByteBuffer dst)
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = sc.read(buffer);//读取个数
        //4.将buffer转换为普通数组
        byte[] arr = buffer.array();
        //5.输出客户端数据
        //这里操作的是普通数组arr,不是缓冲区,所以不用切换读模式
        System.out.println(new String(arr,0,len));
        //6.释放资源
        sc.close();
        ssc.close();
    }
}

小结:

1.创建服务器套接字对象:

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(9999));

2.abstract SocketChannel accept() 接受到此通道套接字的连接。

3.解决上述accept阻塞的问题

package com.itheima.sh.net_channel_02;

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


public class ClientDemo03 {
    public static void main(String[] args) throws IOException {
        //1.创建客户端通道对象连接服务器  static SocketChannel open(SocketAddress remote)
        //InetSocketAddress(String hostname, int port)
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 12306));
        //关闭流
        sc.close();
    }
}


package com.itheima.sh.net_channel_02;

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

/*
    解决accept方法阻塞问题:

 */
public class ServerDemo03 {
    public static void main(String[] args) throws Exception{
        //1.创建ServerSocketChannel对象
        //1.1 使用ServerSocketChannel类的方法:static ServerSocketChannel open()
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //1.2 ServerSocketChannel bind(SocketAddress local)
        //使用子类InetSocketAddress(int port) 指定服务器的端口号
        ssc.bind(new InetSocketAddress(12306));

        //2.设置非阻塞模式:
        /*
            abstract  SelectableChannel configureBlocking(boolean block) 调整此通道的阻塞模式。
                参数是false表示非阻塞
         */
        ssc.configureBlocking(false);

        //3.获取客户端
        //由于这里设置了非阻塞模式,那么如果没有客户端,该accept方法返回null
//        System.out.println("111111");
//        SocketChannel sc = ssc.accept();
//        System.out.println("222222"+sc);
        //使用死循环
        while(true){
            SocketChannel sc = ssc.accept();
            //判断sc是否等于null
            if(sc == null){
                //说明没有客户端
                System.out.println("没有客户端连接,玩会");
                Thread.sleep(2000);
            }else{
                //说明有客户端
                System.out.println("连接上了客户端");
                break;
            }
        }
    }
}

小结:

我们可以使用ServerSocketChannel的父类中的方法设置为服务器为非阻塞方式:

 abstract  SelectableChannel configureBlocking(boolean block) 调整此通道的阻塞模式。
         参数是false表示非阻塞

3.Selector选择器(掌握)

1.选择器介绍

nio三大组件:

1.buffer 缓冲区 负责存储数据

2.channel 通道 负责建立连接

3.selector 选择器 主要负责一个选择器可以监听多个通道 是非阻塞的核心

阻塞模式:

在这里插入图片描述

非阻塞模式:

在这里插入图片描述

小结:

1.使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势

2.多路复用:一个selector可以监听多个服务器端口号

2.Selector选择器的使用

package com.itheima.sh.selector_03;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;

/*
    客户端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建客户端通道对象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 12306));

        //释放资源
        sc.close();

    }
}

package com.itheima.sh.selector_03;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

/*
    选择器的使用:
        1.选择器使用Selector表示,属于抽象类
        2.获取选择器:
            static Selector open() 打开一个选择器。
        3.将连接通道注册到选择器:
            使用服务器端的套接字通道ServerSocketChannel的父类SelectableChannel中的注册方法:
                 SelectionKey register(Selector sel, int ops)  向给定的选择器注册此通道,返回一个选择键。
                    参数:
                        sel:表示被注册的选择器
                        ops:所得键的可用操作集 所得键就是SelectionKey,该类表示 SelectableChannel(通道) 在 Selector(选择器) 中的注册的标记
                            SelectionKey选择键的成员变量:
                                static int OP_ACCEPT 用于套接字【接受】操作的操作集位。
                                        说明:我们使用服务器套接字ServerSocketChannel和客户端建立连接,这里必须指定 OP_ACCEPT,否则就会报错
                                static int OP_CONNECT 用于套接字【连接】操作的操作集位。
                                static int OP_READ 用于【读取】操作的操作集位。
                                static int OP_WRITE 用于【写入】操作的操作集位。
       4.注册到选择器上的channel必须是非阻塞模式,通过ServerSocketChannel的父类SelectableChannel方法:
                abstract  SelectableChannel configureBlocking(boolean block)  false表示非阻塞
       5.方法:abstract  int select() 选择一组键,其相应的通道已为 I/O 操作准备就绪
                说明:等待客户端访问,如果没有客户端访问,那么此时一直等待客户端,只要有客户端访问,如果
                服务器不做处理,那么就不会等待了。
                返回值表示获取到的客户端数量
 */
public class ServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.创建服务器的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //2.绑定端口号
        ssc.bind(new InetSocketAddress(12306));

        //3.设置为非阻塞模式 abstract  SelectableChannel configureBlocking(boolean block)  false表示非阻塞
        ssc.configureBlocking(false);
        //4.获取选择器
        Selector selector = Selector.open();

        //5.将通道注册到选择器上
        /*
             使用服务器端的套接字通道ServerSocketChannel的父类SelectableChannel中的注册方法:
             SelectionKey register(Selector sel, int ops)  向给定的选择器注册此通道,返回一个选择键。

                    注册方法第二个参数:
                            ops:所得键的可用操作集 所得键就是SelectionKey,该类表示 SelectableChannel(通道) 在
                            Selector(选择器) 中的注册的标记
                            SelectionKey选择键的成员变量:
                                static int OP_ACCEPT 用于套接字【接受】操作的操作集位。
         */
        ssc.register(selector, SelectionKey.OP_ACCEPT);//表示将通道ssc注册到选择器上,就是将侦听并获取客户端的accept()方法交给了选择器
        /*
            方法:abstract  int select() 选择一组键,其相应的通道已为 I/O 操作准备就绪
                说明:等待客户端访问,如果没有客户端访问,那么此时一直等待客户端,只要有客户端访问,如果
                服务器不做处理,那么就不会等待了。
                返回值表示获取到的客户端数量
         */
       /* System.out.println("1");
        //6.使用选择器对象调用选择器类的方法abstract  int select() 等待客户端
        int count = selector.select();
        System.out.println("2");
        System.out.println("count = " + count);*/

       while(true){
           System.out.println("1");
           //6.使用选择器对象调用选择器类的方法abstract  int select() 等待客户端
           /*
                只要有客户端访问,如果服务器不做处理,那么就不会等待了。
                如果在服务器中处理了客户端,那么select()方法就会等待下个客户端访问
            */
           int count = selector.select();
           //休眠
           Thread.sleep(2000);
           System.out.println("2");
           System.out.println("count = " + count);
           /*
                处理客户端
            */
           Set<SelectionKey> keys = selector.selectedKeys();
           for (SelectionKey key : keys) {
               ServerSocketChannel ssc2 = (ServerSocketChannel) key.channel();
               SocketChannel sc = ssc2.accept();
           }
       }


    }
}

小结:

选择器使用步骤:

1.创建服务器通道:

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(12306));

2.设置为非阻塞

ssc.configureBlocking(false);

3.创建选择器

Selector selector = Selector.open();

4.将通道注册到选择器上

ssc.register(selector, SelectionKey.OP_ACCEPT);

5.使用选择器操作通道

3.Selector选择器方法

  • abstract Set selectedKeys() :当客户端来连接服务器之时,Selector会把【被连接】的服务器对象放到Set集合中。

说明:

1.SelectionKey表示 SelectableChannel(通道) 在 Selector (选择器)中的注册的关系。 是一个类

  • Set keys() 将服务器的所用对象放到set集合中

  • 代码演示:

    package com.itheima.sh.selector_04;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.util.Set;
    
    /*
        1.Selector选择器中的方法:
            1)abstract  Set<SelectionKey> selectedKeys()  :当客户端来连接服务器之时,Selector会把被连接的**服务器对象**放到Set集合中。
            2)Set<SelectionKey> keys() 将服务器的所用对象放到set集合中
    
     */
    public class ServerDemo01 {
        public static void main(String[] args) throws IOException {
            //1.创建服务器对象
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.bind(new InetSocketAddress(9999));
    
            ServerSocketChannel ssc2 = ServerSocketChannel.open();
            ssc2.bind(new InetSocketAddress(8888));
    
    
            //2.设置为非阻塞模式
            ssc.configureBlocking(false);
            ssc2.configureBlocking(false);
            //3.获取选择器
            Selector selector = Selector.open();
            //4.将通道ssc注册到选择器上
            //多个通道注册到同一个选择器上
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            ssc2.register(selector, SelectionKey.OP_ACCEPT);
            //5.abstract  Set<SelectionKey> selectedKeys()  :当客户端来连接服务器之时,Selector会把被连接的**服务器对象**放到Set集合中。
            Set<SelectionKey> set1 = selector.selectedKeys();
            //set1集合中的服务器对象个数:0
            System.out.println("set1集合中的服务器对象个数:"+set1.size());
    
            //获取所有的服务器对象
            Set<SelectionKey> set2 = selector.keys();
            //set2集合中的服务器对象个数:2
            System.out.println("set2集合中的服务器对象个数:"+set2.size());
            //6.调用select方法连接客户端
            selector.select();
            //输出
            //set集合中的服务器对象个数:1
            System.out.println("set集合中的服务器对象个数:"+set1.size());
            //set2集合中的服务器对象个数:2
            System.out.println("set2集合中的服务器对象个数:"+set2.size());
    
    
        }
    }
    
    

    小结:

    1.Set selectedKeys() 将被连接的服务器对象放到set集合中

    2.Set keys() 将服务器的所用对象放到set集合中

4.使用Selector选择器接收来自客户端的数据并打印服务器端

客户端:

package com.itheima.sh.selector_05;

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

/*
    客户端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建客户端对象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 7777));
        //2.创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.添加数据
        buffer.put("大家好,我是nio,我来了,我难吗".getBytes());
        //4.切换读模式
        buffer.flip();
        //5.写数据
        sc.write(buffer);
        //6.释放资源
        sc.close();
    }
}

服务器端:

package com.itheima.sh.selector_05;

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

/*
    服务器端
    使用Selector选择器接收来自客户端的数据并打印服务器端
 */
public class ServerDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建服务器对象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(7777));

        //2.设置非阻塞
        ssc.configureBlocking(false);

        //3.创建选择器
        Selector selector = Selector.open();

        //4.将通道注册到选择其实上
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        //5.连接客户端
        selector.select();
        //6.获取被连接的服务器对象放到set集合
        Set<SelectionKey> set1 = selector.selectedKeys();
        System.out.println("被连接的服务器对象个数:"+set1.size());

        //7.遍历集合
        for (SelectionKey key : set1) {//key属于SelectionKey类型表示通道ssc和选择器之间的关系
            //8.abstract  SelectableChannel channel()返回为之创建此键的通道。  SelectableChannel 是ServerSocketChannel的父类
            ServerSocketChannel ss = (ServerSocketChannel) key.channel();//表示服务器通道
            System.out.println("11111");
            //9.获取客户端通道
            SocketChannel sc = ss.accept();
            System.out.println("22222");
            //10.创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //11.读取客户端发送的数据
            int len = sc.read(buffer);
            //12.将buffer转换为普通数组
            byte[] arr = buffer.array();
            //13.输出客户端发送的数据
            System.out.println(new String(arr,0,len));
        }
        //14.释放资源
        ssc.close();
    }
}

5.Selector选择器管理多个通道

客户端:

package com.itheima.sh.selector_06;

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

/*
    客户端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建客户端对象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
        //2.创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.添加数据
        buffer.put("大家好,我是nio,我来了,我难吗".getBytes());
        //4.切换读模式
        buffer.flip();
        //5.写数据
        sc.write(buffer);
        //6.释放资源
        sc.close();
    }
}

服务器:

package com.itheima.sh.selector_06;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Set;

/*
    服务器端
    Selector选择器管理多个通道
 */
public class ServerDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建服务器对象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(7777));

        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        ssc1.bind(new InetSocketAddress(8888));

        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        ssc2.bind(new InetSocketAddress(9999));

        //2.设置非阻塞
        ssc.configureBlocking(false);
        ssc1.configureBlocking(false);
        ssc2.configureBlocking(false);

        //3.创建选择器
        Selector selector = Selector.open();

        //4.将通道注册到选择器上
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);

        //加死循环模拟一直运行
        while(true){
            //5.连接客户端
            System.out.println("11111");
            selector.select();

            //6.获取所有被连接的服务器对象
            Set<SelectionKey> set1 = selector.selectedKeys();
            //            System.out.println("服务器个数:"+set1.size());
            //            //7.遍历set集合
            for (SelectionKey key : set1) {
                //8.获取服务器对象
                ServerSocketChannel ss = (ServerSocketChannel) key.channel();
                //9.取出客户端
                SocketChannel s = ss.accept();
                System.out.println("s = " + s);
                //10.定义缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //11.使用客户端对象s调用读取方法读取客户端的数据放到buffer中
                int len = s.read(buffer);
                //12.转换普通数组
                byte[] arr = buffer.array();
                //13.输出
                System.out.println(new String(arr,0,len));
            }
        }


    }
}

问题图解:

在这里插入图片描述

产生上述异常原因:

​ Selector把被连接的服务器对象放在了一个Set集合中,但是使用完后并没有删除。导致在遍历集合时,遍历到了已经没用的对象,出现了异常。

解决上述问题:

package com.itheima.sh.selector_06;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/*
    服务器端
    Selector选择器管理多个通道
 */
public class ServerDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建服务器对象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(7777));

        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        ssc1.bind(new InetSocketAddress(8888));

        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        ssc2.bind(new InetSocketAddress(9999));

        //2.设置非阻塞
        ssc.configureBlocking(false);
        ssc1.configureBlocking(false);
        ssc2.configureBlocking(false);

        //3.创建选择器
        Selector selector = Selector.open();

        //4.将通道注册到选择器上
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);

        //加死循环模拟一直运行
        while (true) {
            //5.连接客户端
            System.out.println("11111");
            selector.select();

            //6.获取所有被连接的服务器对象
            Set<SelectionKey> set1 = selector.selectedKeys();
            System.out.println("服务器个数:" + set1.size());
            //            //7.遍历set集合
            /*
                对于set1集合来说,取出每个服务器对象使用完毕之后需要将其从set集合中删除
                注意删除集合中的数据,我们这里是增强for循环,原理是Iterator迭代器,删除集合数据不能使用集合中的删除方法,
                否则会报并发修改异常,使用Iterator迭代器中的删除方法
             */
            //获取迭代器对象
            Iterator<SelectionKey> it = set1.iterator();
            while (it.hasNext()) {
                //取出SelectionKey
                SelectionKey key = it.next();
//                for (SelectionKey key : set1) {//8888 9999
                //8.获取服务器对象
                ServerSocketChannel ss = (ServerSocketChannel) key.channel();
                //9.取出客户端
                SocketChannel s = ss.accept();
                System.out.println("s = " + s);
                //10.定义缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //11.使用客户端对象s调用读取方法读取客户端的数据放到buffer中
                int len = s.read(buffer);
                //12.转换普通数组
                byte[] arr = buffer.array();
                //13.输出
                System.out.println(new String(arr, 0, len));
                //删除服务器对象 使用迭代器中的删除方法
                it.remove();
//            }
            }
//
        }


    }
}

小结:每次处理完客户端之后,都使用迭代器中的删除方法将对应的服务器对象从set集合中删除。

4.NIO2-AIO(异步、非阻塞)(理解)

概念介绍

从jdk7开始引入的技术,称为NIO2英文全称:Asynchronous I/O 异步的IO.异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

1.同步:调用方法要有返回值。

2.异步:调用方法没有返回值,并且可以支持回调函数。回调函数就是回过头来在调用的函数,有底层调用的,我们只负责编写代码。

3.阻塞:不执行其他操作,一直等待。

4.非阻塞:不一直等待,可以执行其他操作。

上述四个概念举例:

在这里插入图片描述

AIO同步写法【听下就可以了,用不到】

客户端:

package com.itheima.sh.aio_07;

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

/*
    客户端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建客户端对象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 12306));
        //2.创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.添加数据
        buffer.put("大家好,我是Aio,我来了,我难吗".getBytes());
        //4.切换读模式
        buffer.flip();
        //5.写数据
        sc.write(buffer);
        //6.释放资源
        sc.close();
    }
}

服务端:

package com.itheima.sh.aio_07;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/*
    服务器:
    AIO同步(有返回值)写法
    使用步骤:
    1.创建aio的服务器AsynchronousServerSocketChannel的对象:
        static AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。
        AsynchronousServerSocketChannel bind(SocketAddress local) 绑定端口  SocketAddress属于抽象类,我们使用子类InetSocketAddress(端口号)
    2.侦听并获取客户端套接字:
        abstract Future<AsynchronousSocketChannel> accept() 接受连接
        说明:
            1)该方法将接收的客户端存储到Future<V>接口中,需要使用该接口中的V get() 取出客户端
            2) AsynchronousSocketChannel表示客户端套接字
            3)上述accept方法有返回值,属于同步的
     3.使用客户端套接字对象调用方法读取客户端的数据
            abstract Future<Integer> read(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
                说明:
                    1)该方法将接收的客户端的数据存储到Future<V>接口中,需要使用该接口中的V get() 取出客户端请求的	数据
                    2)上述read方法有返回值,属于同步
 */
public class ServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.创建AIO的服务器对象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        //2.绑定端口号
        assc.bind(new InetSocketAddress(12306));
        //3.侦听并获取客户端套接字:
        //future中存放的是客户端套接字AsynchronousSocketChannel对象,我们需要调用get方法获取
        Future<AsynchronousSocketChannel> f = assc.accept();
        AsynchronousSocketChannel ascoket = f.get();

        //4.使用客户端套接字对象调用方法读取客户端的数据
        //  abstract Future<Integer> read(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        Future<Integer> f2 = ascoket.read(buffer);
        //取出数据
        Integer len = f2.get();//读取的字节个数
        //5.将字节缓冲区变为普通数组
        byte[] arr = buffer.array();
        //6.输出
        System.out.println(new String(arr,0,len));

        ascoket.close();
        assc.close();
    }
}

小结:

1.AsynchronousServerSocketChannel表示AIO中的服务器套接字对象

2.获取对象:

static AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。
AsynchronousServerSocketChannel bind(SocketAddress local) 绑定端口

3.侦听并获取客户端套接字:

 abstract Future<AsynchronousSocketChannel> accept() 接受连接

4.使用客户端套接字对象调用方法读取客户端的数据

 abstract Future<Integer> read(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。

AIO异步非阻塞连接(理解)

客户端:

package com.itheima.sh.aio_08;

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

/*
    客户端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建客户端对象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));

        //6.释放资源
        sc.close();
    }
}

服务器:

package com.itheima.sh.aio_08;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.Future;

/*
    服务器
    编写AIO的异步方式:

 */
public class ServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.创建AIO的服务器对象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        assc.bind(new InetSocketAddress(9999));

        //2.异步非阻塞接收客户端
        /*
            abstract <A> void accept​(A attachment, CompletionHandler<AsynchronousSocketChannel> handler)
                        参数:
                            attachment:要附加到I / O操作的对象; 可以是null
                            handler:属于CompletionHandler接口类型,表示消耗结果的处理程序 ,这里保存的是客户端套接字
                                方法:
                                    void completed​(V result, A attachment) 操作完成后调用。  回调函数
                                    void failed​(Throwable exc, A attachment) 当操作失败时调用。
         */
        System.out.println("111111");
        assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            // 操作完成后调用。  回调函数:回过头来调用的函数,底层调用的
            /*
                result:表示客户端
                attachment:就是null
             */
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                System.out.println("completed.......");
            }
            // 当操作失败时调用
            @Override
            public void failed(Throwable exc, Object attachment) {

            }
        });

        System.out.println("222222");
        //为了能够让上述回调函数执行,这里使用死循环,让jvm一直运行
        while (true){

        }
    }
}

AIO异步非阻塞连接并读取客户端的数据(理解)

客户端:

package com.itheima.sh.aio_09;

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

/*
    客户端
 */
public class ClientDemo01 {
    public static void main(String[] args) throws IOException {
        //1.创建客户端对象
        SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 12306));
        //2.创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.添加数据
        buffer.put("大家好,我是Aio,我来了,我难吗".getBytes());
        //4.切换读模式
        buffer.flip();
        //5.写数据
        sc.write(buffer);
        //6.释放资源
        sc.close();
    }
}

服务器端:

package com.itheima.sh.aio_09;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.Future;

/*
    服务器:
    AIO异步,读取客户端的数据
 */
public class ServerDemo01 {
    public static void main(String[] args) throws Exception {
        //1.创建服务器对象
        AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
        assc.bind(new InetSocketAddress(12306));

        //2.异步非阻塞接收客户端
        System.out.println(1);
        assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            //接收的客户端
            //socket表示客户端
            @Override
            public void completed(AsynchronousSocketChannel socket, Object attachment) {
                System.out.println(3);
                //如果想要异步读取客户端数据
                /*
                    <A> void read​(ByteBuffer dst, A attachment, CompletionHandler<Integer,? super A> handler)
                                参数:
                                    dst:要传输字节的缓冲区 存储数据的字节缓冲区
                                    attachment:要附加到I / O操作的对象; 可以是null
                                    handler:完成处理程序,属于CompletionHandler接口类型
                                        抽象方法:
                                            void completed​(V result, A attachment) 操作完成后调用。
                                            void failed​(Throwable exc, A attachment) 当操作失败时调用。
                 */
                //创建字节数组
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                socket.read(buffer, null, new CompletionHandler<Integer, Object>() {
                    //读取成功的方法
                    //len表示read读取的字节个数
                    @Override
                    public void completed(Integer len, Object attachment) {
                        System.out.println(5);
                        //将字节缓冲区变为普通的字节数组
                        byte[] arr = buffer.array();
                        System.out.println(new String(arr,0,len));
                    }

                    @Override
                    public void failed(Throwable exc, Object attachment) {
                    }
                });
                System.out.println(4);
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
            }
        });
        System.out.println(2);
        //模拟服务器一直运行
        while (true) {
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

娃娃 哈哈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值