BIO, NIO,AIO

BIO、NIO、AIO概述

1.BIO、NIO与AIO概述

1).BIO:Block(阻塞的) IO——。 【同步、阻塞】
2).NIO:Non-Block(非阻塞的(同步)IO——JDK1.4开始的。 【同步、非阻塞】
3).AIO:Asynchronous(异步-非阻塞)IO——JDK1.7开始 【异步、非阻塞】

2.阻塞和非阻塞,同步和异步的概念

举个例子,比如我们去照相馆拍照,拍完照片之后,商家说需要30分钟左右才能洗出来照片

  • 同步+阻塞

    这个时候如果我们一直在店里面啥都不干,一直等待商家面前等待它洗完照片,这个过程就叫同步阻塞。

  • 同步+非阻塞

    当然大部分人很少这么干,更多的是大家拿起手机开始看电视,看一会就会问老板洗完没,老板说没洗完,然后我们接着看,再过一会接着问(轮询),直到照片洗完,这个过程就叫同步非阻塞。

  • 异步+阻塞

    因为店里生意太好了,越来越多的人过来拍,店里面快没地方坐了,老板说你把你手机号留下,我一会洗好了就打电话告诉你过来取,然后你去外面找了一个长凳开始躺着睡觉等待老板打电话,啥不都干,这个过程就叫异步阻塞。

  • 异步+非阻塞

    当然实际情况是,大家可能会直接先去逛街或者吃饭做其他的活动,同时等待老板打电话,这样以来两不耽误,这个过程就叫异步非阻塞。

总结

从上面的描述中我们其实能够看到阻塞和非阻塞通常是指客户端在发出请求后,在服务端处理这个请求的过程中,客户端本身是否直接挂起等待结果(阻塞),还是继续做其他的任务(非阻塞)。
而异步和同步,则是对于请求结果的获取是客户端主动等待获取(同步),还是由服务端来通知消息结果(异步)。
从这一点来看同步和阻塞其实描述的两个不同角度的事情,阻塞和非阻塞指的一个是客户端等待消息处理时的本身的状态,是挂起还是继续干别的。同步和异步指的对于消息结果的获取是客户端主动获取,还是由服务端间接推送。

NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程
首先,我们要先了解一下NIO的三个主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)

Buffer类(缓冲区)

1.Buffer概述

  • java.nio.Buffer(抽象类):用于特定原始类型(基本类型)的数据的容器。后期在会用Channel进行通信时,底层全部使用Buffer。
  • 它的几个子类:
    1.ByteBuffer:里面可以封装一个byte[]数组。
    2.ShortBuffer:里面可以封装一个short[]数组。
    3.CharBuffer:里面可以封装一个char[]数组
    4.IntBuffer:里面可以封装一个int[]数组。
    5.LongBuffer:里面可以封装一个long[]数组。
    6.FloatBuffer:里面可以封装一个float[]数组。
    7.DoubleBuffer:里面可以封装一个double[]数组。

2.创建ByteBuffer

  • 没有构造方法可以创建ByteBuffer,可以通过它的一些“静态方法”获取ByteBuffer对象。
  • 常用三个静态方法: new byte[10]; 默认值 0,0,0…0
    • public static ByteBuffer allocate(int capacity):使用一个“容量”来创建一个“间接字节缓存区”——程序的“堆”空间中创建。
    • public static ByteBuffer allocateDirect(int capacity):使用一个“容量”来创建一个“直接字节缓存区”——系统内存。 {1,2,3,4,5}
    • public static ByteBuffer wrap(byte[] byteArray):使用一个“byte[]数组”创建一个“间接字节缓存区”。
  • 代码演示

import java.nio.ByteBuffer;

/*
    java.nio.ByteBuffer:字节缓冲区。
    创建对象的方式:使用静态方法获取
 */
public class Demo01ByteBuffer {
    public static void main(String[] args) {
        ByteBuffer buffer1 = ByteBuffer.allocate(100);//里边包含一个byte[100] 间接

        ByteBuffer buffer2 = ByteBuffer.allocateDirect(100);//里边包含一个byte[100] 直接

        ByteBuffer buffer3 = ByteBuffer.wrap("我爱java".getBytes());//里边包含一个byte内容是"我爱java" 间接
    }
}

3.向ByteBuffer添加数据

  • public ByteBuffer put(byte b):向当前可用位置添加数据。
  • public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
  • public ByteBuffer put(byte[] byteArray,int offset,int len):添加一个byte[]数组的一部分
  • byte[] array()获取此缓冲区的 byte 数组
import java.nio.ByteBuffer;
import java.util.Arrays;

/*
    向ByteBuffer中添加数据
    - public ByteBuffer put(byte b):向当前可用位置添加数据。
    - public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
    - public ByteBuffer put(byte[] byteArray,int offset,int len):添加一个byte[]数组的一部分
 */
public class Demo02ByteBuffer {
    public static void main(String[] args) {
        //创建一个长度为10的ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(10);
        System.out.println(Arrays.toString(buffer.array()));//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

        //public ByteBuffer put(byte b):向当前可用位置添加数据。
        buffer.put((byte)1);
        buffer.put((byte)2);
        System.out.println(Arrays.toString(buffer.array()));//[1, 2, 0, 0, 0, 0, 0, 0, 0, 0]

        //public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
        byte[] bytes = {10,20,30,40,50};
        buffer.put(bytes);
        System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 0, 0, 0]

        buffer.put(bytes,3,2);
        System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 40, 50, 0]

        //put(int index, byte b) 往指定索引处添加一个byte字节
        buffer.put(9,(byte)88);
        System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 40, 50, 88]
    }
    
}

4.容量-capacity

  • Buffer的容量(capacity)是指:Buffer所能够包含的元素的最大数量。定义了Buffer后,容量是不可变的。
  • 示例代码:
import java.nio.ByteBuffer;

public class Demo03capacity {
    public static void main(String[] args) {
        //创建一个长度为10的ByteBuffer,创建完之后长度不能改变,底层是数组
        ByteBuffer buffer1 = ByteBuffer.allocate(10);
        System.out.println("容量:"+buffer1.capacity());//容量:10

        ByteBuffer buffer2 = ByteBuffer.wrap("你好".getBytes());
        System.out.println("容量:"+buffer2.capacity());//容量:6
    }
}

5.限制-limit

  • 限制:limit:表示如果设置“限制为某一个位置,那么此位置后的位置将不可用”。
  • 有两个相关方法:
    • public int limit():获取此缓冲区的限制。
    • public Buffer limit(int newLimit):设置此缓冲区的限制。
  • 示例代码:
import java.nio.ByteBuffer;

public class Demo04Limit {
    public static void main(String[] args) {
        ByteBuffer buf = ByteBuffer.allocate(10);
        System.out.println("容量:" + buf.capacity() + " 限制:" + buf.limit());//容量:10 限制:10
        buf.limit(3);//显示3索引位置之后将不可用,包含3

        buf.put((byte)0);
        buf.put((byte)1);
        buf.put((byte)2);
        System.out.println("容量:" + buf.capacity() + " 限制:" + buf.limit());//容量:10 限制:3
        //buf.put((byte)3);//BufferOverflowException
    }
}

6.位置-position

  • 位置position是指:当前可写入的索引。位置不能小于0,并且不能大于"限制"。
  • 有两个相关方法:
    • public int position():获取当前可写入位置索引。
    • public Buffer position(int p):更改当前可写入位置索引。
  • 示例代码:
public class Demo05Position {
    public static void main(String[] args) {
        ByteBuffer buf = ByteBuffer.allocate(10);

        System.out.println("初始容量:" + buf.capacity() +//10
                " 初始限制:" + buf.limit() +//10
                " 当前位置:" + buf.position());//0

        buf.put((byte) 10);//position = 1
        buf.put((byte) 20);//position = 2
        buf.put((byte) 30);//position = 3
        System.out.println("当前容量:" + buf.capacity() +
                " 初始限制:" + buf.limit() +
                " 当前位置:" + buf.position());//3
        System.out.println(Arrays.toString(buf.array()));//[10, 20, 30, 0, 0, 0, 0, 0, 0, 0]

        buf.position(1);//当position改为:1
        buf.put((byte) 2);//添加到索引:1
        buf.put((byte) 3);//添加到索引:2
        System.out.println(Arrays.toString(buf.array()));//[10, 2, 3, 0, 0, 0, 0, 0, 0, 0]

        buf.limit(3);
        buf.put((byte) 4);//添加到索引:3 BufferOverflowException  position<limit
    }
}

7.标记-mark

  • 标记mark是指:当调用缓冲区的reset()方法时,会将缓冲区的position位置重置为该索引。不能为0,不能大于position。
  • 相关方法:
    • public Buffer mark():设置此缓冲区的标记为当前的position位置。
  • 示例代码:
import java.nio.ByteBuffer;
import java.util.Arrays;

public class Demo06mark {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put((byte)0);//position=1
        buffer.mark();//设置标记为1
        buffer.put((byte)1);//position=2
        buffer.put((byte)2);//position=3
        System.out.println("position:"+buffer.position());//position:3
        System.out.println(Arrays.toString(buffer.array()));//[0, 1, 2, 0, 0, 0, 0, 0, 0, 0]

        buffer.reset();//设置position的位置为mark标记的位置
        System.out.println("position:"+buffer.position());//position:1
        buffer.put((byte)100);
        System.out.println(Arrays.toString(buffer.array()));//[0, 100, 2, 0, 0, 0, 0, 0, 0, 0]
    }
}

2.8 其它方法

  • public int remaining():获取position与limit之间的元素数。
  • public boolean isReadOnly():获取当前缓冲区是否只读。
  • public boolean isDirect():获取当前缓冲区是否为直接缓冲区。
  • public Buffer clear():还原缓冲区的状态。
    • 将position设置为:0
    • 将限制limit设置为容量capacity;
    • 丢弃标记mark。
  • public Buffer flip():缩小limit的范围。 获取读取的有效数据0到position之间的数据
    • 将limit设置为当前position位置;
    • 将当前position位置设置为0;
    • 丢弃标记。
import java.nio.ByteBuffer;

public class Demo07other {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put((byte)0);//position=1
        buffer.put((byte)1);//position=2
        buffer.put((byte)2);//position=3
        //- public int remaining():获取position与limit之间的元素数。
        System.out.println(buffer.remaining());//7[3-9]
        //- public boolean isReadOnly():获取当前缓冲区是否只读。
        System.out.println(buffer.isReadOnly());//false 既可以读,有可以写
        //- public boolean isDirect():获取当前缓冲区是否为直接缓冲区。
        System.out.println(buffer.isDirect());//false

        ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
        System.out.println(buffer2.isDirect());//true
    }
}

Channel(通道)

1.Channel概述

1).java.nio.channels.Channel(接口):用于 I/O 操作的连接。

  • 表示:通道。
  • 可以是“文件通道-FileChannel”、“网络通道-SocketChannel和ServerSockecChannel”。
  • 它类似于IO流,但比IO流更强大。read(byte[]) write(byte[])
  • IO流是“单向”的,Channel是“双向的”。

2).Channel全部使用Buffer实现读、写。read(ByteBuffer) write(ByteBuffer)

2.FileChannel类的基本使用

  • java.nio.channels.FileChannel (抽象类):用于读、写文件的通道。
  • FileChannel是抽象类,我们可以通过FileInputStream和FileOutputStream的getChannel()方法方便的获取一个它的子类对象。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/*
    java.nio.channels.FileChannel:用于读取、写入、映射和操作文件的通道。
    获取对象的方式:
        FileInputStream类中的方法:
            FileChannel getChannel() 返回与此文件输入流有关的唯一 FileChannel 对象。
        FileOutputStream类中的方法:
            FileChannel getChannel() 返回与此文件输出流有关的唯一 FileChannel 对象。
    成员方法:
        int read(ByteBuffer dst) :读取多个字节到ByteBuffer中,相当于与FileInputStream中的方法read(byte[])
        int write(ByteBuffer src) : 将ByteBuffer中的数据写入到文件中,相当于FileOutputStream类中的方法write(byte[])
    实现步骤:
        1.创建FileInputStream对象,绑定要读取的数据源
        2.创建FileOutputStream对象,绑定要输出的目的地
        3.使用FileInputStream对象中的方法getChannel,获取输入的FileChannel对象
        4.使用FileOutputStream对象中的方法getChannel,获取输出的FileChannel对象
        5.使用FileChannel对象中的方法read,读取文件
        6.使用FileChannel对象中的方法write,把读取到的数据写入到文件中
        7.释放资源
 */
public class Demo01FileChannel {
    public static void main(String[] args) throws IOException {
        //1.创建FileInputStream对象,绑定要读取的数据源
        FileInputStream fis = new FileInputStream("c:\\1.jpg");
        //2.创建FileOutputStream对象,绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
        //3.使用FileInputStream对象中的方法getChannel,获取输入的FileChannel对象
        FileChannel fisChannel = fis.getChannel();
        //4.使用FileOutputStream对象中的方法getChannel,获取输出的FileChannel对象
        FileChannel fosChannel = fos.getChannel();
        //5.使用FileChannel对象中的方法read,读取文件
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = 0;
        while ((len = fisChannel.read(buffer))!=-1){
            //使用flip方法缩小limit的范围:最后一次读取不一定读取1024个
            buffer.flip();
            //6.使用FileChannel对象中的方法write,把读取到的数据写入到文件中
            fosChannel.write(buffer);//注:写的时候会修改position的位置
            //初始化ByteBuffer的状态
            buffer.clear();
        }
        //7.释放资源
        fosChannel.close();
        fisChannel.close();
        fos.close();
        fis.close();
    }
}

3.FileChannel结合MappedByteBuffer实现高效读写

  • java.io.RandomAccessFile类
获取FileChannel需要使用RandomAccessFile类,可以创建流对象的同时设置读写模式
java.io.RandomAccessFile类,可以设置读、写模式的IO流类
构造方法:
	RandomAccessFile(String name, String mode)
	参数:
		String name:要读取的数据源,或者写入的目的地
		String mode:设置流的读写模式
			"r":只读,必须是小写
			"rw":读写,必须是小写
成员方法:
	FileChannel getChannel() 返回与此文件关联的唯一 FileChannel 对象。
  • 使用FileChannel类中的方法map获取MappedByteBuffer
- MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)  将此通道的文件区域直接映射到内存中。
	参数:
		FileChannel.MapMode mode:设置读写的模式
			READ_ONLY:只读映射模式。
			READ_WRITE:读取/写入映射模式。
	   long position:文件中的位置,映射区域从此位置开始,一般都是从0开始
	   size - 要映射的区域大小,就是要复制文件的大小,单位字节
  • java.nio.MappedByteBuffer类
java.nio.MappedByteBuffer:它可以创建“直接缓存区”,将文件的磁盘数据映射到内存。
注意:它最大可以映射:Integer.MAX_VALUE个字节(2G)左右。
eg:磁盘和内存实时映射 硬盘(abc) 内存(abc)  内存修改为(ab) 磁盘也跟着修改(ab)
MappedByteBuffer中的方法:
	byte get(int index)  获取缓冲区中指定索引处的字节
	ByteBuffer put(int index, byte b)  把字节写入到指定的索引处

在这里插入图片描述

  • 代码实现:复制2g以下的文件
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/*
    FileChannel结合MappedByteBuffer实现高效读写
    注意:
        MappedByteBuffer:直接缓存区,最大设置缓冲区大小为2G(2048M)
        复制的文件不能超过2G的大小,超过了就需要分块复制
    需求:
        使用MappedByteBuffer复制2g以下的文件
    实现步骤:
        1.创建读取文件的RandomAccessFile对象,构造方法中封装要读取的数据源和只读模式
        2.创建写入文件的RandomAccessFile对象,构造方法中封装要写入的目的地和读写模式
        3.使用读取文件的RandomAccessFile对象中的方法getChannel,获取读取文件的FileChannel
        4.使用写入文件的RandomAccessFile对象中的方法getChannel,获取写入文件的FileChannel
        5.使用读取文件的FileChannel中的方法size,获取要读取文件的大小(字节)
        6.使用读取文件的FileChannel中的方法map,创建读取文件的直接缓冲区MappedByteBuffer
        7.使用写入文件的FileChannel中的方法map,创建写入文件的直接缓冲区MappedByteBuffer
        8.创建for循环,循环size次
        9.使用读取文件的直接缓冲区MappedByteBuffer中的方法get,读取指定索引处的字节
        10.使用写入文件的直接缓冲区MappedByteBuffer中的方法put,把读取到的字节写入到指定索引处
        11.释放资源
 */
public class Demo01MappedByteBuffer {
    public static void main(String[] args) throws Exception {
        long s = System.currentTimeMillis();
        //1.创建读取文件的RandomAccessFile对象,构造方法中封装要读取的数据源和只读模式(模式必须写小写字母)
        RandomAccessFile inRAF = new RandomAccessFile("c:\\748m.rar","r");
        //2.创建写入文件的RandomAccessFile对象,构造方法中封装要写入的目的地和读写模式
        RandomAccessFile outRAF = new RandomAccessFile("d:\\748m.rar","rw");
        //3.使用读取文件的RandomAccessFile对象中的方法getChannel,获取读取文件的FileChannel
        FileChannel inChannel = inRAF.getChannel();
        //4.使用写入文件的RandomAccessFile对象中的方法getChannel,获取写入文件的FileChannel
        FileChannel outChannel = outRAF.getChannel();
        //5.使用读取文件的FileChannel中的方法size,获取要读取文件的大小(字节)
        long size = inChannel.size();
        System.out.println("要复制的文件大小:"+size);//要复制的文件大小:785042177
        //6.使用读取文件的FileChannel中的方法map,创建读取文件的直接缓冲区MappedByteBuffer
        MappedByteBuffer inMap = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
        //7.使用写入文件的FileChannel中的方法map,创建写入文件的直接缓冲区MappedByteBuffer
        MappedByteBuffer outMap = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
        //8.创建for循环,循环size次
        for (int i = 0; i < size; i++) {
            //9.使用读取文件的直接缓冲区MappedByteBuffer中的方法get,读取指定索引处的字节
            byte b = inMap.get(i);
            //10.使用写入文件的直接缓冲区MappedByteBuffer中的方法put,把读取到的字节写入到指定索引处
            outMap.put(i,b);
        }
        //11.释放资源
        outRAF.close();
        inRAF.close();
        long e = System.currentTimeMillis();
        System.out.println("复制文件共耗时:"+(e-s)+"毫秒!");//复制文件共耗时:2224毫秒!
    }
}
  • 代码实现:复制2g以上的文件

在这里插入图片描述

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

/*
    使用MappedByteBuffer复制2G以上的文件
    分析:
        需要分块复制,每块大小512M
 */
public class Demo03MappedByteBuffer {
    public static void main(String[] args) throws Exception {
        long s = System.currentTimeMillis();
        //1.创建读取的RandomAccessFile对象,绑定要读取的数据源和设置只读模式
        RandomAccessFile inraf = new RandomAccessFile("c:\\2g.rar","r");
        //2.创建写入的RandomAccessFile对象,绑定要写入的目的地和设置读写模式
        RandomAccessFile outraf = new RandomAccessFile("d:\\2g.rar","rw");
        //3.使用读取的RandomAccessFile对象中的方法getChannel获取可以读取的FileChannel对象
        FileChannel inchannel = inraf.getChannel();
        //4.使用写入的RandomAccessFile对象中的方法getChannel获取可以写入的FileChannel对象
        FileChannel outchannel = outraf.getChannel();
        //5.使用FileChannel对象中的方法size,获取要读取文件的大小
        long size = inchannel.size();
        //System.out.println(size);
        long count= 1;//共复制的块数,默认值为1
        long startIndex = 0;//复制文件的开始索引
        long copySize = size;//复制每块文件大小,默认等于总大小
        long everSize = 1024*1024*512;//每块文件大小,默认512M
        //判断要复制的文件大小是否大于每块文件的大小
        if(size>everSize){
            //计算共需要复制的总块数
            count = size%everSize==0 ? size/everSize : size/everSize+1;

            //第一次复制文件的大小等于每块大小
            copySize=everSize;
        }

        //6.使用读取的FileChannel对象中的方法map,获取读取的MappedByteBuffer对象
        MappedByteBuffer inmbb = null;
        //7.使用写入的FileChannel对象中的方法map,获取写入的MappedByteBuffer对象
        MappedByteBuffer outmbb = null;
        //分几块就复制几次
        for (int i = 0; i < count; i++) {
            inmbb = inchannel.map(FileChannel.MapMode.READ_ONLY, startIndex, copySize);
            outmbb = outchannel.map(FileChannel.MapMode.READ_WRITE, startIndex, copySize);
            System.out.println("每次复制文件的开始索引:"+startIndex);
            System.out.println("每次复制文件的大小:"+copySize);
            //复制文件
            for (int j = 0; j < copySize; j++) {
                byte b = inmbb.get(j);
                outmbb.put(j,b);
            }
            //计算下一块的起始索引
            startIndex +=copySize;
            //计算下一快复制文件的大小
            copySize = size-startIndex>everSize ? everSize : size-startIndex;
        }
        outraf.close();
        inraf.close();
        long e = System.currentTimeMillis();
        System.out.println("复制文件共耗时:"+(e-s)+"毫秒!");//复制文件共耗时:4611毫秒!
    }
}

4.ServerSocketChannel和SocketChannel创建连接

同步阻塞实现

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

/*
    实现同步非阻塞的服务器
    相关的类:
        java.nio.channels.ServerSocketChannel:针对面向流的侦听套接字的可选择通道。
    获取对象的方式:
        static ServerSocketChannel open() 打开服务器套接字通道。
    成员方法:
        ServerSocketChannel bind(SocketAddress local) 给服务器绑定端口号
        SocketChannel accept() 监听客户端的请求。
        SelectableChannel configureBlocking(boolean block) 设置阻塞模式,true:阻塞; false:非阻塞
    实现步骤:
        1.获取ServerSocketChannel对象
        2.使用ServerSocketChannel对象中的bind绑定指定的端口号
        3.使用ServerSocketChannel对象中的accept方法监听客户端的请求
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.获取ServerSocketChannel对象
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2.使用ServerSocketChannel对象中的bind绑定指定的端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));
        //3.使用ServerSocketChannel对象中的accept方法监听客户端的请求
        System.out.println("服务器等待客户端连接...");
        SocketChannel socketChannel = serverSocketChannel.accept();
        System.out.println("有客户端连接服务器...");
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;

/*
    实现同步非阻塞的客户端
    相关的类:
        java.nio.channels.SocketChannel
    获取对象的方法:
        static SocketChannel open() 打开套接字通道。
    成员方法:
        boolean connect(SocketAddress remote) 根据服务器的ip地址和端口号连接服务器
        configureBlocking​(boolean block)设置阻塞模式,true:阻塞; false:非阻塞
    实现步骤:
        1.获取SocketChannel对象
        2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1.获取SocketChannel对象
        SocketChannel socketChannel = SocketChannel.open();
        //2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
    }
}

服务器同步非阻塞实现

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //设置服务器非阻塞模式
        serverSocketChannel.configureBlocking(false);
        System.out.println("服务器等待客户端连接...");
        SocketChannel socketChannel = serverSocketChannel.accept();//默认阻塞
        System.out.println("有客户端连接服务器...");
    }
}

服务器轮询监听客户端

public class TCPServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.获取ServerSocketChannel对象
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2.使用ServerSocketChannel对象中的bind绑定指定的端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));
        //3.使用ServerSocketChannel对象中的accept方法监听客户端的请求
        //设置服务器非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //轮询监听客户端
        while (true){
            System.out.println("服务器等待客户端连接...");
            SocketChannel socketChannel = serverSocketChannel.accept();//默认阻塞
            //对象SocketChannel进行判断,没有客户端连接返回null
            if(socketChannel!=null){
                System.out.println("有客户端连接服务器...");
                break;//结束轮询
            }else{
                System.out.println("没有客户端连接服务器,休息2秒,干点其他事,继续轮询...");
                Thread.sleep(2000);
            }
        }
    }
}

执行结果:

服务器等待客户端连接...
没有客户端连接服务器,休息2,继续轮询...
服务器等待客户端连接...
没有客户端连接服务器,休息2,继续轮询...
服务器等待客户端连接...
有客户端连接服务器...

客户端实现同步非阻塞:

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

/*
    实现同步非阻塞的客户端
    相关的类:
        java.nio.channels.SocketChannel
    获取对象的方法:
        static SocketChannel open() 打开套接字通道。
    成员方法:
        boolean connect(SocketAddress remote) 根据服务器的ip地址和端口号连接服务器
        configureBlocking(boolean block)设置阻塞模式,true:阻塞; false:非阻塞
    实现步骤:
        1.获取SocketChannel对象
        2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
    注意:
        客户端使用connect连接服务器
            客户端设置为阻塞模式,会多次尝试连接,连接成功connect方法返回true连接不成功抛出连接异常
            客户端设置为非阻塞模式,只会连接一次服务器,connect方法返回false
 */
public class TCPClient {
    public static void main(String[] args) throws InterruptedException {
        //阻塞轮询连接服务器
        while (true){
            try{
                //1.获取SocketChannel对象
                SocketChannel socketChannel = SocketChannel.open();
                socketChannel.configureBlocking(true);//默认true:阻塞
                //socketChannel.configureBlocking(false);//false:非阻塞
                //2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
                boolean b = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
                System.out.println(b);
                System.out.println("连接服务器成功,结束轮询...");
                break;//结束轮询
            }catch (Exception e){
                System.out.println("连接服务器失败,休息2秒,干点别的...");
                Thread.sleep(2000);
            }
        }
    }
}

5.ServlerSocketChannel和SocketChannel收发信息

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

/*
    客户端给服务器发送数据,接收服务器回写的数据
        int write(ByteBuffer src) 给服务器发送数据
        long read(ByteBuffer[] dsts) 读取服务器发送的数据
 */
public class TCPClient {
    public static void main(String[] args) throws InterruptedException {
        //ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
        //System.out.println("容量:"+buffer.capacity());//15
        //System.out.println("索引:"+buffer.position());//0
        //System.out.println("限定:"+buffer.limit());//15
        //阻塞轮询连接服务器
        while (true){
            try{
                //1.获取SocketChannel对象
                SocketChannel socketChannel = SocketChannel.open();
                socketChannel.configureBlocking(true);//默认true:阻塞
                //socketChannel.configureBlocking(false);//false:非阻塞
                //2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
                socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
                //int write​(ByteBuffer src) 给服务器发送数据
                ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
                System.out.println("容量:"+buffer.capacity());//15
                System.out.println("索引:"+buffer.position());//0
                System.out.println("限定:"+buffer.limit());//15
                socketChannel.write(buffer);

                //long read(ByteBuffer[] dsts) 读取服务器发送的数据
                ByteBuffer buffer2 = ByteBuffer.allocate(1024);
                int read = socketChannel.read(buffer2);
                buffer2.flip();//缩小limit的范围
                String msg = new String(buffer2.array(),0,buffer2.limit());
                System.out.println("客户端收到服务器发送的数据:"+msg);
                break;//结束轮询
            }catch (Exception e){
                System.out.println("连接服务器失败,休息2秒,干点别的...");
                Thread.sleep(2000);
            }
        }
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/*
    服务器接收客户端发送的数据,给客户端回写数据
        long read(ByteBuffer[] dsts) 读取客户端发送的数据
        int write(ByteBuffer src) 给客户端发送数据
 */
public class TCPServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.获取ServerSocketChannel对象
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2.使用ServerSocketChannel对象中的bind绑定指定的端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));
        //3.使用ServerSocketChannel对象中的accept方法监听客户端的请求
        //设置服务器非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //轮询监听客户端
        while (true){
            System.out.println("服务器等待客户端连接...");
            SocketChannel socketChannel = serverSocketChannel.accept();//默认阻塞
            //对象SocketChannel进行判断,没有客户端连接返回null
            if(socketChannel!=null){
                System.out.println("有客户端连接服务器...");
                //long read(ByteBuffer[] dsts) 读取客户端发送的数据
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int len = socketChannel.read(buffer);
                buffer.flip();//缩小limit的范围
                String msg = new String(buffer.array(),0,buffer.limit());
                System.out.println("服务器接收到客户端发送的信息:"+msg);
                
                //int write​(ByteBuffer src) 给客户端发送数据
                ByteBuffer buffer2 = ByteBuffer.wrap("收到谢谢".getBytes());
                socketChannel.write(buffer2);
                break;//结束轮询
            }else{
                System.out.println("没有客户端连接服务器,休息2秒,干点其他事,继续轮询...");
                Thread.sleep(2000);
            }
        }
    }
}

Selector(选择器)

1.多路复用的概念

选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。使用它可以节省CPU资源,提高程序的运行效率。

"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。

  • 服务器端的非多路复用效果

在这里插入图片描述

如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能下降。

  • 服务器端的多路复用效果

在这里插入图片描述

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

2.选择器Selector_服务器端实现多路注册

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

/*
    选择器Selector_服务器端实现多路注册
    java.nio.channels.Selector:SelectableChannel对象的多路复用器。
        获取Selector对象的方式:
            static Selector open​() 打开选择器。
    注册Channel到Selector:ServerSocketChannel中的方法
        SelectionKey register(Selector,SelectionKey.OP_READ);
        参数:
            SelectionKey:意思是在通过Selector监听Channel时对什么事件感兴趣
            接收就绪--常量:SelectionKey.OP_ACCEPT      (ServerSocketChannel在注册时只能使用此项)
    实现步骤:
    1.创建3个ServerSocketChannel对象
    2.分别给3个ServerSocketChannel对象绑定不同的端口号
    3.设置3个ServerSocketChannel对象为非阻塞
    4.获取Selector对象
    5.把3个ServerSocketChannel对象注册到Selector对象
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.创建3个ServerSocketChannel对象
        ServerSocketChannel channel = ServerSocketChannel.open();
        ServerSocketChannel channe2 = ServerSocketChannel.open();
        ServerSocketChannel channe3 = ServerSocketChannel.open();
        //2.分别给3个ServerSocketChannel对象绑定不同的端口号
        channel.bind(new InetSocketAddress(7777));
        channe2.bind(new InetSocketAddress(8889));
        channe3.bind(new InetSocketAddress(9999));
        //3.设置3个ServerSocketChannel对象为非阻塞
        channel.configureBlocking(false);
        channe2.configureBlocking(false);
        channe3.configureBlocking(false);
        //4.获取Selector对象
        Selector selector = Selector.open();
        //5.把3个ServerSocketChannel对象注册到Selector对象
        channel.register(selector, SelectionKey.OP_ACCEPT);
        channe2.register(selector, SelectionKey.OP_ACCEPT);
        channe3.register(selector, SelectionKey.OP_ACCEPT);
    }
}

3.选择器Selector_常用方法

  • Selector的keys()方法
    • 此方法返回一个Set集合,表示:已注册通道的集合。每个已注册通道封装为一个SelectionKey对象。
  • Selector的selectedKeys()方法
    • 此方法返回一个Set集合,表示:当前已连接的通道的集合。每个已连接通道同一封装为一个SelectionKey对象。
  • Selector的select()方法
    • 此方法会阻塞,直到有至少1个客户端连接。
    • 此方法会返回一个int值,表示有几个客户端连接了服务器。
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;

public class TCPServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.创建3个ServerSocketChannel对象
        ServerSocketChannel channel = ServerSocketChannel.open();
        ServerSocketChannel channe2 = ServerSocketChannel.open();
        ServerSocketChannel channe3 = ServerSocketChannel.open();
        //2.分别给3个ServerSocketChannel对象绑定不同的端口号
        channel.bind(new InetSocketAddress(7777));
        channe2.bind(new InetSocketAddress(8888));
        channe3.bind(new InetSocketAddress(9999));
        //3.设置3个ServerSocketChannel对象为非阻塞
        channel.configureBlocking(false);
        channe2.configureBlocking(false);
        channe3.configureBlocking(false);
        //4.获取Selector对象
        Selector selector = Selector.open();
        //5.把3个ServerSocketChannel对象注册到Selector对象
        channel.register(selector, SelectionKey.OP_ACCEPT);
        channe2.register(selector, SelectionKey.OP_ACCEPT);
        channe3.register(selector, SelectionKey.OP_ACCEPT);

        //循环接收客户端的连接
        while (true){
            //Selector的select()方法:获取连接客户端的数量,没有获取到客户端的连接对象,此方法将阻塞
            int count = selector.select();
            System.out.println("连接客户端的数量:"+count);

            //Selector的keys()方法:获取已经注册的通道集合
            Set<SelectionKey> keys = selector.keys();
            System.out.println("已经注册的通道的数量:"+keys.size());

            //Selector的selectedKeys()方法:获取已经连接的通道集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("已经连接的通道的数量:"+selectionKeys.size());

            //获取完一个客户端的连接,睡眠2秒
            Thread.sleep(2000);
        }
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;

/*
    创建两个线程,连接服务器
 */
public class TCPClient {
    public static void main(String[] args) {
        //启动两个线程,分别连接7777和8888端口
        new Thread(()->{
            while (true) {
                try (SocketChannel socket = SocketChannel.open()) {
                    System.out.println("7777客户端连接服务器......");
                    socket.connect(new InetSocketAddress("localhost", 7777));
                    System.out.println("7777客户端连接成功....");
                    break;
                } catch (IOException e) {
                    System.out.println("7777异常重连");
                }
            }
        }).start();

        new Thread(()->{
            while (true) {
                try (SocketChannel socket = SocketChannel.open()) {
                    System.out.println("8888客户端连接服务器......");
                    socket.connect(new InetSocketAddress("localhost", 8888));
                    System.out.println("8888客户端连接成功....");
                    break;
                } catch (IOException e) {
                    System.out.println("8888异常重连");
                }
            }
        }).start();
    }
}

4.选择器Selector_多路信息接收


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.Iterator;
import java.util.Set;

/*
    选择器Selector_服务器端实现多路注册
    java.nio.channels.Selector:SelectableChannel对象的多路复用器。
        获取Selector对象的方式:
            static Selector open​() 打开选择器。
    注册Channel到Selector:ServerSocketChannel中的方法
        SelectionKey register(Selector,SelectionKey.OP_READ);
        参数:
            SelectionKey:意思是在通过Selector监听Channel时对什么事件感兴趣
            接收就绪--常量:SelectionKey.OP_ACCEPT      (ServerSocketChannel在注册时只能使用此项)
    实现步骤:
    1.创建3个ServerSocketChannel对象
    2.分别给3个ServerSocketChannel对象绑定不同的端口号
    3.设置3个ServerSocketChannel对象为非阻塞
    4.获取Selector对象
    5.把3个ServerSocketChannel对象注册到Selector对象
 */
public class TCPServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.创建3个ServerSocketChannel对象
        ServerSocketChannel channell = ServerSocketChannel.open();
        ServerSocketChannel channel2 = ServerSocketChannel.open();
        ServerSocketChannel channel3 = ServerSocketChannel.open();
        //2.分别给3个ServerSocketChannel对象绑定不同的端口号
        channell.bind(new InetSocketAddress(7777));
        channel2.bind(new InetSocketAddress(8888));
        channel3.bind(new InetSocketAddress(9999));
        //3.设置3个ServerSocketChannel对象为非阻塞
        channell.configureBlocking(false);
        channel2.configureBlocking(false);
        channel3.configureBlocking(false);
        //4.获取Selector对象
        Selector selector = Selector.open();
        //5.把3个ServerSocketChannel对象注册到Selector对象
        channell.register(selector, SelectionKey.OP_ACCEPT);
        channel2.register(selector, SelectionKey.OP_ACCEPT);
        channel3.register(selector, SelectionKey.OP_ACCEPT);

        //循环接收客户端的连接
        while (true){
            //Selector的select()方法:获取连接客户端的数量,没有获取到客户端的连接对象,此方法将阻塞
            System.out.println("服务器等待客户端连接....");
            int count = selector.select();
            System.out.println("连接客户端的数量:"+count);

            //Selector的selectedKeys()方法:获取已经连接的通道集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("已经连接的通道的数量:"+selectionKeys.size());

            //处理Selector监听到的事件,遍历Set集合,获取每一个SelectionKey对象
            Iterator<SelectionKey> it = selectionKeys.iterator();
            while (it.hasNext()){
                SelectionKey selectionKey = it.next();
                //获取SelectionKey中封装的ServerSocketChannel对象
                ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
                //获取此通道监听的端口号
                System.out.println("监听的端口:"+channel.getLocalAddress());
                //处理accept事件
                SocketChannel socketChannel = channel.accept();
                //读取SocketChannel发送的数据
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int len = socketChannel.read(buffer);
                buffer.flip();//缩小limit的范围
                String msg = new String(buffer.array(),0,buffer.limit());
                System.out.println("接收到客户端的数据为:"+msg);

                //处理完SelectionKey监听到的事件,要移除处理完的SelectionKey,防止空指针异常
                it.remove();
            }


            //获取完一个客户端的连接,睡眠2秒
            Thread.sleep(2000);
        }
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/*
    创建两个线程,连接服务器
 */
public class TCPClient {
    public static void main(String[] args) {
        //启动两个线程,分别连接7777和8888端口
        new Thread(()->{
            while (true) {
                try (SocketChannel socket = SocketChannel.open()) {
                    System.out.println("7777客户端连接服务器......");
                    socket.connect(new InetSocketAddress("localhost", 7777));
                    System.out.println("7777客户端连接成功....");
                    //给服务器发送数据
                    ByteBuffer buffer = ByteBuffer.wrap("你好服务器,我是7777端口的客户端!".getBytes());
                    socket.write(buffer);
                    break;
                } catch (IOException e) {
                    System.out.println("7777异常重连");
                }
            }
        }).start();

        new Thread(()->{
            while (true) {
                try (SocketChannel socket = SocketChannel.open()) {
                    System.out.println("8888客户端连接服务器......");
                    socket.connect(new InetSocketAddress("localhost", 8888));
                    System.out.println("8888客户端连接成功....");
                    //给服务器发送数据
                    ByteBuffer buffer = ByteBuffer.wrap("你好服务器,我是8888端口的客户端!".getBytes());
                    socket.write(buffer);
                    break;
                } catch (IOException e) {
                    System.out.println("8888异常重连");
                }
            }
        }).start();
    }
}

NIO2-AIO(异步、非阻塞)

1.AIO概述

​ JDK7新增的:AsynchronousIO:异步、非阻塞IO

2.AIO异步非阻塞连接

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

/*
    创建AIO的服务器端:
    相关的类:
        java.nio.channels.AsynchronousServerSocketChannel:用于面向流的侦听套接字的异步通道。
    获取对象的方法:
        static AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。
    成员方法:
        void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler)
        参数:
            A attachment:附件,可以传递null
            CompletionHandler handler:事件处理类,用于处理accept方法监听到的事件
            CompletionHandler也叫回调函数,客户端请求服务器之后,会自动执行CompletionHandler接口中的方法
            CompletionHandler接口中的方法:
                void completed(V result, A attachment);客户端连接成功执行的方法
                void failed(Throwable exc, A attachment);客户端连接失败执行的方法
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建异步非阻塞服务器AsynchronousServerSocketChannel对象
        AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
        //给AsynchronousServerSocketChannel对象绑定指定的端口号
        channel.bind(new InetSocketAddress(8888));
        System.out.println("accept方法开始执行....");
        //使用AsynchronousServerSocketChannel对象中的方法accept监听请求的客户端对象
        channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            //客户端连接成功执行的方法
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                System.out.println("有客户端连接成功!");
            }
            //客户端连接失败执行的方法
            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("有客户端连接失败!");
            }
        });
        System.out.println("accept方法执行结束....");
        while (true){}
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousSocketChannel;

/*
    创建AIO的客户端:
    和客户端相关的类:
        java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
    获取对象的方法:
        static AsynchronousSocketChannel open​() 打开异步套接字通道。
    成员方法:
        Future<Void> connect​(SocketAddress remote) 连接此频道。
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //创建异步非阻塞客户端AsynchronousSocketChannel对象
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        //使用AsynchronousSocketChannel对象中的方法connect连接服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
    }
}

3. AIO异步连接:同步非阻塞读写

import java.io.IOException;
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.ExecutionException;
import java.util.concurrent.Future;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建异步非阻塞服务器AsynchronousServerSocketChannel对象
        AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
        //给AsynchronousServerSocketChannel对象绑定指定的端口号
        channel.bind(new InetSocketAddress(8888));
        System.out.println("accept方法开始执行....");
        //使用AsynchronousServerSocketChannel对象中的方法accept监听请求的客户端对象
        channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            //客户端连接成功执行的方法
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                System.out.println("有客户端连接成功!");
                //获取客户端中发送的数据
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                Future<Integer> future = result.read(buffer);//同步非阻塞的read方法
                Integer len = null;
                try {
                    len = future.get();//读取客户端中发送的信息
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                String msg = new String(buffer.array(),0,len);
                System.out.println("服务器读取到客户端的信息:"+msg);
            }
            //客户端连接失败执行的方法
            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("有客户端连接失败!");
            }
        });
        System.out.println("accept方法执行结束....");
        while (true){}
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;

/*
    创建AIO的客户端:
    和客户端相关的类:
        java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
    获取对象的方法:
        static AsynchronousSocketChannel open​() 打开异步套接字通道。
    成员方法:
        Future<Void> connect​(SocketAddress remote) 连接此频道。
        Future<Integer> read​(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
        Future<Integer> write​(ByteBuffer src) 从给定的缓冲区向该通道写入一个字节序列。
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //创建异步非阻塞客户端AsynchronousSocketChannel对象
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        //使用AsynchronousSocketChannel对象中的方法connect连接服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
        //连接服务器成功,给服务器发送数据
        ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
        socketChannel.write(buffer);
    }
}

4.AIO异步连接:异步非阻塞读写

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建异步非阻塞的服务器
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
        //给服务器绑定指定的端口号
        serverSocketChannel.bind(new InetSocketAddress(7777));
        System.out.println("开始执行accpet方法....");
        //使用accept监听客户端的请求:CompletionHandler叫回调函数,有客户端请求服务器,就会执行CompletionHandler里边的方法
        serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                System.out.println("客户端连接成功执行的方法!");
                //使用read方法读取客户端发送的数据
                //Future<Integer> read​(ByteBuffer dst)
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //Future<Integer> future = result.read(buffer);//read是一个阻塞的方法

                /*
                    void read​(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer,? super A> handler) //是一个非阻塞的方法
                    参数
                        dst - 要传输字节的缓冲区
                        timeout - 完成I / O操作的最长时间
                        unit - timeout参数的时间单位
                        attachment - 要附加到I / O操作的对象; 可以是null
                        handler - 消费结果的处理程序
                 */
                result.read(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
                    @Override
                    public void completed(Integer result, Object attachment) {
                        System.out.println("读取客户端发送数据成功执行的方法");
                       buffer.flip();
                        String msg = new String(buffer.array(),0,buffer.limit());
                        System.out.println("服务器接收到客户端的信息:"+msg);
                    }

                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        System.out.println("读取客户端发送数据失败,执行的方法!");
                    }
                });
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("客户端连接失败执行的方法!");
            }
        });
        System.out.println("结束执行accpet方法....");
        //去做别的事情
        while (true){

        }
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;

public class TCPClient {
    public static void main(String[] args) throws IOException, InterruptedException {
        //创建异步非阻塞客户端AsynchronousSocketChannel对象
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        //使用AsynchronousSocketChannel对象中的方法connect连接服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
        //连接服务器成功,给服务器发送数据
        ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
        Thread.sleep(1000*11);
        socketChannel.write(buffer);
    }
}

执行结果:

accept方法开始执行....
accept方法执行结束....
有客户端连接成功!
read方法开始执行.....
read方法执行结束.....
服务器读取到客户端的信息:你好服务器
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值