NIO模型


通过BIO+ 多线程方式可以解决掉多用户连接的问题,随着用户连接的增多,就会无限的创建新的线程处理用户连接但是线程的不能无限创建的,因为

  1. 线程创建多时,系统需要调度的线程就会多,线程的上下文切换会消耗很多的系统性能
  2. 每一个线程都有自己的栈空间,假如每一个线程占用1M的栈空间,创建1000个线程,需要1000M

怎么解决这个问题?

可以通过线程池来解决、复用线程达到减少线程创建的目的,假如线程池提供4个线程来做处理,假如线程池中4个线程都在read数据,进入到阻塞状态。这个时候是不能接收新用户的连接。


一:NIO模型简介

NIO模型最大的特点是将IO 的系统调用设置为非阻塞,进行系统调用时 ,如果数据准备好则会立即拿到数据执行,如果数据没有准备,会立即返回一个状态码(返回-1,ERR:),用户可以做自己事情,在一定时间需要继续查询

IO复用模型,是系统提供了(select、poll)方式用来同时监听多个用户的请求,一旦有事件完成则将结果通知给用户线程进行处理

NIO模型:说的是系统调用会立即返回

IO复用是指复用器select等同时监听多个用户的连接

IO复用结合BIO来,常使用的是IO复用+NIO模型来使用,才是真正意义上同步非阻塞模型

NIO中提供了选择器(Selector 类似底层操作系统提供的IO复用器:select、poll、epoll),也叫做多路复用器,作用是检查一个或者多个NIO Channel(通道)的状态是否是可读、可写······可以实现单线程管理多个channel,也可以管理多个网络请求

Channel:通道,用于IO操作的连接,在Java.nio.channels包下定义的,对原有IO的一种补充,不能直接访问数据需要和缓冲区Buffer进行交互
通道主要实现类:
SocketChannel:通过TCP读写网络中的数据,一般客户端的实现
ServerSocketChannel:监听新进来的TCP连接,对每一个连接都需要创建一个SocketChannel,一般是服务端的实现
Buffer:缓冲区

IO流中的数据需要经过缓冲区交给Channel
在这里插入图片描述

buffer的作用就是用户和channel通道进行数据交流的桥梁


二:NIO编程

服务端

public class Server {

    public static void main(String[] args) {
        //创建服务端ServerSocketChannel实例
        ServerSocketChannel serverSocketChannel;
        {
            try {
                serverSocketChannel = ServerSocketChannel.open();

                //绑定端口
                serverSocketChannel.bind(new InetSocketAddress(6666));
                System.out.println("服务端启动啦");

                //监听

                //设置serverSocketChannel为非阻塞
                serverSocketChannel.configureBlocking(false);

                //创建selector复用器
                Selector selector = Selector.open();

                //将监听事件注册到复用器上
                serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

                //等待系统返回已完成的事件
                //select()本身是会阻塞,等系统告诉用户空间那些事件已经准备就绪,返回结果表示已准备完成的事件个数

                while (selector.select() > 0) {
                    //感兴趣事件集合(指的就是注册到selector中的事件)
                    Set <SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator <SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey selectionKey = iterator.next();
                        //删除掉已经完成的事件
                        iterator.remove();
                        if (selectionKey.isAcceptable()) {
                            //当前是可接收事件已经准备就绪
                            ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();

                            //接收客户端连接,返回一个SocketChannel实例表示是客户端的连接
                            SocketChannel socketChannel = serverSocketChannel1.accept();
                            System.out.println("客户端连接上");

                            //设置SocketChannel实例为非阻塞
                            socketChannel.configureBlocking(false);

                            //将SocketChannel注册到复用器上,并关注读事件
                            socketChannel.register(selector,SelectionKey.OP_READ);
                        }

                        if (selectionKey.isReadable()) {
                            //当前有可读事件发生
                            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                            //读数据
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            //往Buffer中写数据
                            int read = socketChannel.read(buffer);
                            if (read == -1) {
                                //客户端已经关闭
                                socketChannel.close();
                                continue;
                            }
                            //进行读写模式的切换
                            buffer.flip();

                            byte[] bytes = new byte[buffer.remaining()];
                            //读取buff数据
                            buffer.get(bytes);
                            //接收数据
                            String msg = new String(bytes);

                            System.out.println("客户端:"+socketChannel.getRemoteAddress()+" 发送数据"+msg);


                            String recv = "[echo]:"+msg;
                            //回复消息

                            //先将Buffer清空
                            buffer.clear();

                            //往Buffer写数据
                            buffer.put(recv.getBytes());

                            //读写模式切换
                            buffer.flip();
                            //将Buffer数据读到channel通道
                            socketChannel.write(buffer);

                            //业务断开
                            if ("exit".equals(msg)) {
                                socketChannel.close();
                            }
                        }
                    }
                }

                //来轮序是什么事件完成


            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

1、创建ServerSocketChannel实例
2、对通道serverSocketChannel进行端口绑定bind
3、将通道设置为非阻塞configureBlocking设置为false
4、创建复用器实例Selector(Selector.open())
5、将serverSocketChannel注册到复用器上,并关注ACCEPT事件
6、等待系统返回已完成事件集合(select)
7、通过遍历感兴趣事件集合
8、如果是accept事件完成,则进行accept操作,接收客户端连接,将客户端连接channel设置为非阻塞,并关注read事件
9、循环第6步,
10、如果是read事件完成,则进行读数据操作

客户端

public class Client {
    public static void main(String[] args) {
        try {
        //创建SocketChannel通道
            SocketChannel socketChannel = SocketChannel.open();

            //设置socketChannel为非阻塞
            socketChannel.configureBlocking(false);

            //实例化复用器
            Selector selector = Selector.open();

            //连接服务端,该connect不会阻塞,会立即返回 boolean true:连接成功  false:表示还未连接成功
            if (!socketChannel.connect(new InetSocketAddress("127.0.0.1", 6666))){
                //表示连接不成功 当前正在连接,将当前的可连接事件交给内核帮助监听
                socketChannel.register(selector,SelectionKey.OP_CONNECT);

                //等待连接完成
                selector.select();
                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    if (selectionKey.isConnectable()) socketChannel.finishConnect();
                }
            }

            //要么是连接成功
            //给服务端发送数据
            Scanner scanner = new Scanner(System.in);
            String msg = null;

            //注册读事件
            socketChannel.register(selector,SelectionKey.OP_READ);

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while ((msg =scanner.nextLine()) != null) {
                //重复性读操作
                buffer.clear();

                msg +="\n";
                //将数据写入到Buffer中
                buffer.put(msg.getBytes());
                //读写模式切换
                buffer.flip();

                //将数据从 Buffer中写入channel通道,对于Buffer而言,是读取数据
                socketChannel.write(buffer);



                //等内核数据准备完成
                selector.select();

                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    if (selectionKey.isReadable()) {
                        SocketChannel channel = (SocketChannel) selectionKey.channel();

                        ByteBuffer allocate = ByteBuffer.allocate(1024);
                        //将数据从通道写入Buffer
                        channel.read(allocate);

                        //读写模式切换
                        allocate.flip();

                        //remaining 实际读取的数据长度
                        byte[] bytes = new byte[allocate.remaining()];


                        //将Buffer数据读到byte数组中
                        allocate.get(bytes);


                        String recv = new String(bytes);

                        System.out.println(recv);

                    }
                }

                //判断是否结束
                if ("exit".equals(msg)) {
                    break;
                }


            }



            //关闭资源
            selector.close();
            socketChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

1、创建SocketChannel的通道实例
2、将通道SocketChannel设置为非阻塞
3、实例化复用器(Selector.open)
4、主动连接服务端(connect),当前会立即返回,返回结果为true,则表示连接成功
5、如果返回为false,将通道socketChannel注册到复用器中,等待系统返回连接成功。调用finishConnect
6、和服务端进行读写操作(通过通道进行读写,需要借助Buffer)


问题1:客户端为什么主动connect连接

在这里插入图片描述
在BIO中connect操作是一个阻塞方法,在NIO中设置为非阻塞,交给IO复用器来进行监听关注的事件是否完成(CONNECT事件),内核来帮助监听感兴趣事件是否完成,前提是事件必须触发,发生之后内核才能够监听(connect),在NIO中SocketChannel是设置为非阻塞,当前操作会立即返回,当调用connect之后,会立即返回一个Boolean类型的结果,表示是连接成功还是正在连接重,当前的connect事件才触发,触发之后内核才能帮助监听,当然前提是将connect事件注册到复用器上,内核才能关注到该事件。


问题2:客户端断开连接之后,服务端一致循环接口有可读事件,且为空

在这里插入图片描述
客户端断开连接,服务端会接收到-1,占用空间,服务端认为是有数据可以读取,就会一直有可读事件发生需要服务端处理,判断通道接收是否为-1,是则结束接收


问题3:为什么写没有注册到复用器上

写操作在NIO中也是一个事件,注意:写事件是需要主动发起写操作,一般写完之后立即write操作不会进行阻塞,即通常写操作并不需要注册。


三:NIO模型详解

1、channel(通道)

channel是和用户的操作IO相连,但是通道不能直接使用(需要使用Buffer)

读操作:从channel里读取的数据通过Buffer交给用户
写操作:将用户要发送的数据通过Buffer交给channel

在这里插入图片描述
channel和流区别时什么?

  1. channel的数据读写操作必须要借助Buffer缓冲,流是不需要的
  2. channel是即可以读数据也可以写数据,流是单向的,要么读,要么写

使用示例:读操作

在这里插入图片描述
channel的主要实现类

  • FileChannel:用户读取,写入文件的通道
  • SocketChannel:通过TCP读写网络中的数据通道,一般客户端的实现,主要用来连接服务端(connect)
  • ServerSocketChannel:通过TCP来读写网络中的数据通道,一般是服务端实现,用来监听(accept)客户端通道的连接
  • DatagramChannel:通过UDP来读写网络中的数据通道

2、Buffer 缓冲区

Java NIO 的 Buffer 用于和 **NIO Channel(通道)**交互。数据是从通道读入缓冲区,从缓冲区写入到通道中。缓冲区本质上是块可以写入数据,再从中读数据的内存。该内存被包装成 NIO 的 Buffer 对象,并提供了一系列方法,方便开发者访问该块内存

(1)基本用法

使用Buffer读写数据一般四步走:

  1. 写数据到 Buffer
  2. 调用 buffer.flip()
  3. 从 Buffer 中读取数据
  4. 调用 clear() 或 compact()

当向 buffer 写数据时,buffer 会记录写了多少数据。一旦要读取数据,需通过 flip() 将 Buffer 从写模式切到读模式。在读模式下,可读之前写到 buffer 的所有数据。一旦读完数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear()compact() 方法。
5. clear() 会清空整个缓冲区
6. compact() 只会清除已经读过的数据。任何未读数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

使用示例

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {

  buf.flip();  //make buffer ready for read

  while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
  }

  buf.clear(); //make buffer ready for writing
  bytesRead = inChannel.read(buf);
}
aFile.close();

(2)Buffer实现

Buffer的实现底层是通过特定类型(byte、long…)数组来存储数据
数组中数据的操作需要借助4个指针来操作:

// Invariants: mark <= position <= limit <= capacity
private int mark = -1;   //标记
private int position = 0; //位置
private int limit; //限制
private int capacity; //容量

标记、位置、限制和容量值遵守以下不变式

0<=标记<=位置<=限制<=容量

新创建的缓冲区总有一个0位置和一个未定义的标记。初始限制可以为0,也可以为其他值,这取决于缓冲区类型及其构建方式。一般情况下,缓冲区的初始内容是未定义的。

在这里插入图片描述
capacity:
作为一个内存块,Buffer有个固定大小,即capacity。你只能往里写capacity个byte、long,char等。一旦Buffer满,需将其清空(通过读或清除数据)才能继续往里写数据。

position:
取决于Buffer处在读还是写模式:

  • 写数据到Buffer时,position表示当前位置。初始的position值为0,当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。所以position最大可为capacity–1。
  • 读数据时,也是从某特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

limit:

  • 写模式下,表示最多能往Buffer写多少数据。所以此时limit=capacity。
  • 读模式时, limit表示最多能读到多少数据。

因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。即你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)。


读写实现过程指针变化
在这里插入图片描述


(3)Buffer类型

Buffer是一个抽象类,其实现的子类有ByteBuffer(字节缓冲区)、CharBuffer(字符缓冲区) ·······

Java NIO中Buffer有如下类型:
在这里插入图片描述
这些Buffer类型代表了不同的数据类型,即可通过这些类型来操作缓冲区中的字节。

缓冲区有两种:堆上开辟的空间,堆外开辟的空间


(4)Buffer的分配

要想获得一个Buffer对象首先要进行分配。每个Buffer类都有一个allocate方法。

Buffer的创建:

ByteBuffer为例:

  1. ByteBuffer allocate(int capacity):在堆上创建指定大小的缓冲
  2. ByteBuffer allocateDirect(int capacity):在堆外空间创建指定大小的缓冲
  3. ByteBuffer wrap(byte[] array):通过byte数组实例创建一个缓冲区
  4. ByteBuffer wrap(byte[] array, int offset, int length) :指定byte数据中的内容写入到一个新的缓冲区

在这里插入图片描述


向Buffer写数据

写数据到Buffer有两种方式:

  1. 从Channel写到Buffer
    int bytesRead = inChannel.read(buf);
  2. 通过Buffer的put()方法写到Buffer里
    buf.put(127);

从Buffer读数据

从Buffer读数据有两种方式:

  1. 从Buffer读取数据到Channel
    int bytesWritten = inChannel.write(buf);
  2. 通过Buffer的get()方法从Buffer读取数据
    buf.get();

flip()方法:

flip()方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。


3、Selector(复用器)

选择器或者叫做IO复用器,作用是用来检查一个或者是多个NIO Channel的状态是否是可读,可写…,可以实现单线程管理多个channel,也可以实现多线程管理channel

(1)selector的使用

  1. 创建一个selector的实例
    通过调用selector的open方法实例出对象
    Selector selector = Selector.open();
  2. 注册channel上事件
    注册关注事件,channel提供的register方法,第一个参数传递的是selector实例,第二个参数传递的是感兴趣事件
    serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
  3. 使用selector监听器来监听事件是否完成
    等待内核返回就绪事件
    selector.select();
    阻塞等待内核返回结果,告诉用户那些关注的事件完成
  4. 遍历感兴趣事件集合
    Set <SelectionKey> selectionKeys = selector.selectedKeys();
    返回的是Set类型集合,通过迭代器进行遍历
    感兴趣集合就是已注册并且已准备就绪的事件
  5. 如果还有关注事件,则跳转到第三步继续监听
  6. 最终关闭复用器

(2)SelectableChannel

关于通过SelectionKey的channel()方法返回的类型可以强转为不同的channel类型,如下:

在这里插入图片描述
channel()方法的声明如下:

public abstract SelectableChannel channel();

返回的是SelectableChannel类型

其中ServerSocketChannelSocketChannel类型都是SelectableChannel的子类,SelectableChannel是一个抽象类
在NIO中使用的configureBlockingregister都是定义在SelectableChannel类中的方法,ServerSocketChannelSocketChannel都是直接继承SelectableChannel中的方法


register方法是需要接受两个参数

SelectionKey register(Selector sel, int ops)

第二个参数是“感兴趣的集合”,这个集合是selector需要监听channel对什么事件感兴趣,可以监听的事件类型有四种:connect、read、write、accept事件

(3)SelectionKey

SelectionKey中提供了感兴趣事件,总共有四种

    public static final int OP_READ = 1 << 0;
    public static final int OP_WRITE = 1 << 2;
    public static final int OP_CONNECT = 1 << 3;
    public static final int OP_ACCEPT = 1 << 4;

占用了int的4个bit位

提供的一些方法介绍

boolean isValid() :当前感兴趣事件是否有效
void cancel():取消事件:告诉内核,注册的某一个事件不需要在关注了
boolean isAcceptable() :判断当前是否是可接受事件

selectionKey.channel():返回的是该selectionKey对应的channel
selectionKey.selector();返回该selectionKey对应的selector实例
int readyOps():返回需要监控的可读事件

(4)selector选择过程

三种键集合
已注册键的集合、已选择键的集合、已取消键的集合

  • 已注册键的集合:
    所有的注册到选择器上的事件都会放到已注册事件集合上,包含已经失效的键,通过keys()方法获取
    selector.keys();//已注册事件集合

  • 已选择键的集合:
    已选择键的集合是已注册集合的子集,主要是选择器已经监听准备就绪的键的集合
    通过selectedKeys()方法拿到结果(可能为空)

  • 已取消键的集合:
    是已注册键的集合的子集,包含的是cancel()方法调用多的键,不会是调用cancel方法立即取消,需要先加入到该已取消键的集合


//select() 一直阻塞直至有事件准备就绪才返回 .selectedKeys()不会为空
//select(long timeout) :在指定时间内阻塞,selectedKeys()可能为空
//int selectNow() 不会阻塞,会立即返回 selectedKeys()可能会空

在这里插入图片描述
Java的selector选择过程依赖本地操作系统所提供的IO复用模型
调用select()方法执行过程
在这里插入图片描述

在这里插入图片描述
一个socketchannel的通道,可以立即为对应选择过程中的键,一旦通道注册,键存在已注册键集合,可能存在已选择键的集合和已取消键的集合

一个通道上又可以有读事件、可以有写事件、可连接事件和可接收事件,对应选择过程当中interset集合和read集合

(5)select()方法介绍

//select() :一直阻塞直至有事件准备就绪才返回 .selectedKeys()不会为空
//select(long timeout) :在指定时间内阻塞,selectedKeys()可能为空
//int selectNow() :不会阻塞,会立即返回 selectedKeys()可能会空

返回就绪事件的个数

返回值都是int类型表示的是有多少通道已经就绪,自上一次调用select()方法后有多少个通道变成就绪状态,在之前select()调用时就进入就绪状态的通道不会被基础本次的调用中,在前一次select()调用进入就绪但现在已经不处于就绪的通道也不会被进入,一旦调用select()方法,并且返回值不为0时,则可以通过SelectionKeys()方法来访问已选择键的集合

(6)停止选择方法

  • wakeup():wakeup()方法的调用会使处于阻塞状态的select()方法返回,选择器上第一个还没有返回的选择操作立即返回,如果当前还没有进行中的选择操作,下一次的select()方法的一次调用会立即返回
  • close():通过close关闭seletor操作,使任何一个在选择操作中的阻塞的线程都被唤醒,同时使得注册到selector上的所有的channel被注销,所有的键被取消,但是channel本身不会关闭

四:NIO+多线程


思路:
主线程主要是进行accept事件的关注,子线程完成socketchannel的读写事件的关注


服务端

public class MutilThreadServer {
    public static void main(String[] args) {
        //获取ServerSocketChannel实例
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();


            serverSocketChannel.bind(new InetSocketAddress(9999));
            System.out.println("主线程已启动");

            serverSocketChannel.configureBlocking(false);

            //复用器
            Selector selector = Selector.open();

            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);


            //循环监听
            while (selector.select() > 0) {
                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (!selectionKey.isValid()) continue;

                    if (selectionKey.isAcceptable()) {

                        ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
                        SocketChannel socketChannel = serverSocketChannel1.accept();
                        System.out.println("Thead:"+Thread.currentThread().getName()+",新用户连接:"+socketChannel.getRemoteAddress());

                        //将socketChannel交给子线程进行读写处理
                        new SubThread(socketChannel).start();
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

public class SubThread extends Thread {
    private SocketChannel socketChannel;
    private Selector selector;
    private ByteBuffer buffer;

    public SubThread(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
        try {
            this.selector = Selector.open();
            this.socketChannel.configureBlocking(false);
            this.socketChannel.register(selector, SelectionKey.OP_READ);
            buffer = ByteBuffer.allocate(1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            //关注读事件
            while (selector.select() > 0) {
                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    if (!selectionKey.isValid()) continue;
                    if (selectionKey.isReadable()) {
                        SocketChannel channel = (SocketChannel) selectionKey.channel();

                        //读数据
                        buffer.clear();
                        int read = channel.read(buffer);
                        if (read == -1) {
                            //表示客户端发送完数据
                            channel.close();
                            break;
                        }

                        //读写切换
                        buffer.flip();

                        byte[] bytes = new byte[buffer.remaining()];

                        buffer.get(bytes);

                        String msg = new String(bytes);
                        System.out.println("Thread:"+Thread.currentThread().getName()+"处理用户:"+channel.getRemoteAddress()+",数据:"+msg);

                        //给客户端发送数据
                        buffer.clear();

                        String recv = "[echo]:"+msg+"\n";
                        buffer.put(recv.getBytes());

                        //读写模式切换
                        buffer.flip();

                        channel.write(buffer);

                        if ("exit".equals(msg)) {
                            channel.close();
                            break;
                        }

                    }
                }
            }

        } catch (Exception e) {

        }
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            Selector selector = Selector.open();

            if (!socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999))){
                socketChannel.register(selector,SelectionKey.OP_CONNECT);

                selector.select();
                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    if (selectionKey.isConnectable()) socketChannel.finishConnect();
                }
            }
            Scanner scanner = new Scanner(System.in);
            String msg = null;
            socketChannel.register(selector,SelectionKey.OP_READ);

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while ((msg =scanner.nextLine()) != null) {
                buffer.clear();

                msg +="\n";
                buffer.put(msg.getBytes());
                buffer.flip();

                socketChannel.write(buffer);
                selector.select();

                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    if (selectionKey.isReadable()) {
                        SocketChannel channel = (SocketChannel) selectionKey.channel();

                        ByteBuffer allocate = ByteBuffer.allocate(1024);
                        channel.read(allocate);

                        allocate.flip();

                        byte[] bytes = new byte[allocate.remaining()];

                        allocate.get(bytes);
                        String recv = new String(bytes);
                        System.out.println(recv);
                    }
                }
                if ("exit".equals(msg)) {
                    break;
                }


            }
            selector.close();
            socketChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页