Java_NIO详解

目录

1. Buffer

1.1 定义

HeapByteBuffer

DirectByteBuffer

1.2 Buffer的创建

1.3 Buffer的读取

1.4 Buffer复制 – 浅复制

2. Channel

3. Selector

4. 测试示例(单线程模式)


IO与NIO的区别:

NIO的三大核心:

  • Buffer
  • Channel
  • Selector

1. Buffer

1.1 定义

一个 Buffer 本质上是内存中的一块, 可以将数据写入这块内存, 从这块内存获取数据 

java.nio 定义了以下几个 Buffer 的实现:

Java NIO Buffer三大核心属性:position、limit、capacity

capacity: 作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
position: 当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity.
当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
limit: 在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

(1) capacity代表这个缓冲区的容量,一旦设定就不可以更改。比如 capacity 为 1024 的 IntBuffer,代表其一次可以存放 1024 个 int 类型的值。一旦 Buffer 的容量达到 capacity,需要清空 Buffer,才能重新写入值

(2) 从写操作模式到读操作模式切换的时候(flip),position 都会归零,这样就可以从头开始读写了; 写操作模式下,limit 代表的是最大能写入的数据,这个时候 limit 等于 capacity; 写结束后,切换到读模式,此时的 limit 等于 Buffer 中实际的数据大小,因为 Buffer 不一定被写满了

(3) buffer从分配内存上分为DirectByteBufferHeapByteBuffer

HeapByteBuffer

  • HeapByteBuffer, 标准的java类
  • 维护一份byte[]在JVM堆上
  • 创建开销小

HeapByteBuffer优点:由于内容维护在jvm里,所以把内容写进buffer里速度会快些;并且,可以更容易回收; 

    为什么在使用HeapByteBuffer时读取数据时需要把数据先从堆内存中拷贝到堆外内存,再进行操作?

    因为绝大多数的gc算法(CMS除外)在垃圾回收的时候都会移动内存中的数据,导致发生gc之后内存地址也会发生变化,内核空间在读取数据的时候容易出现问题,所以会先把heap中的内存拷贝到堆外。所以希望优化IO性能的话,数据使用堆外内存可以减少一次内存的拷贝消耗。


DirectByteBuffer

  • 底层存储在非JVM堆上,通过native代码操作
  • -XX:MaxDirectMemorySize=<size>
  • 创建开销大

DirectByteBuffer优点:跟外部设备(IO设备)打交道时会快很多,因为外部设备读取jvm堆里的数据时,不是直接读取的,而是把jvm里的数据读到一个内存块里,再在这个块里读取的,如果使用DirectByteBuffer,则可以省去这一步,实现zero copy(零拷贝);

DirectByteBuffer与HeapByteBuffer的比较:

1.2 Buffer的创建

(1) allocate/allocateDirect 方法

使用示例:

public class BufferCreate {
    public static void main(String[] args) {
        /**
         * 堆内存
         */
        System.out.println("======allocate======");
        ByteBuffer buffer0 = ByteBuffer.allocate(10); 
        if (buffer0.hasArray()) {
            System.out.println("buffer0 array: " + buffer0.array());
            System.out.println("Buffer0 array offset: " + buffer0.arrayOffset());

        }
        System.out.println("Capacity: " + buffer0.capacity());
        System.out.println("Limit: " + buffer0.limit());
        System.out.println("Position: " + buffer0.position());
        System.out.println("Remaining: " + buffer0.remaining());
        System.out.println();
        /**
         * 直接内存
         */
        System.out.println("======allocateDirect======");
        ByteBuffer buffer1 = ByteBuffer.allocateDirect(10); 
        if (buffer1.hasArray()) {  //非数组
            System.out.println("buffer1 array: " + buffer1.array());
            System.out.println("Buffer1 array offset: " + buffer1.arrayOffset());
        }
        System.out.println("Capacity: " + buffer1.capacity());
        System.out.println("Limit: " + buffer1.limit());
        System.out.println("Position: " + buffer1.position());
        System.out.println("Remaining: " + buffer1.remaining());
        System.out.println();
    }
}

控制台输出:

======allocate======
buffer0 array: [B@7852e922
Buffer0 array offset: 0
Capacity: 10
Limit: 10
Position: 0
Remaining: 10

======allocateDirect======
Capacity: 10
Limit: 10
Position: 0
Remaining: 10

(2) wrap方法

使用示例:

public class BufferCreate {
    public static void main(String[] args) {
        byte[] bytes = new byte[10];
        System.out.println("======wrap======");
        ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
        if (buffer2.hasArray()) {
            System.out.println("buffer2 array: " + buffer2.array());
            System.out.println("Buffer2 array offset: " + buffer2.arrayOffset());

        }
        System.out.println("Capacity: " + buffer2.capacity());
        System.out.println("Limit: " + buffer2.limit());
        System.out.println("Position: " + buffer2.position());
        System.out.println("Remaining: " + buffer2.remaining());
        System.out.println();

        byte[] bytes2 = new byte[10];
        ByteBuffer buffer3 = ByteBuffer.wrap(bytes2, 2, 3);
        if (buffer3.hasArray()) {
            System.out.println("buffer3 array: " + buffer3.array());
            System.out.println("Buffer3 array offset: " + buffer3.arrayOffset());
        }
        System.out.println("Capacity: " + buffer3.capacity());
        System.out.println("Limit: " + buffer3.limit());
        System.out.println("Position: " + buffer3.position());
        System.out.println("Remaining: " + buffer3.remaining());
    }
}

控制台输出:

======wrap======
buffer2 array: [B@4e25154f
Buffer2 array offset: 0
Capacity: 10
Limit: 10
Position: 0
Remaining: 10

buffer3 array: [B@70dea4e
Buffer3 array offset: 0
Capacity: 10
Limit: 5
Position: 2
Remaining: 3

1.3 Buffer的读取

  1. put/get()方法: 向buffer中写或者读数据
  2. flip()方法: 将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。
    换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等
  3. mark()/reset()方法: 通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:buffer.mark(); //添加标记 buffer.reset();//恢复到标记位置
  4. compact()/clear()方法: 一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

    如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么使用compact()方法。compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

  5. rewind()方法:​ 将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)

使用示例:

public class BufferAccess {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);  //创建buffer, 默认为写模式
        printBuffer(buffer);
        
        buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
        printBuffer(buffer);

        System.out.println("========切换到读取模式=======");
        buffer.flip(); //切换到读取模式
        printBuffer(buffer);

        //读取buffer
        System.out.println("" + (char) buffer.get() + (char) buffer.get());
        printBuffer(buffer);

        buffer.mark();
        printBuffer(buffer);

        //读取两个元素后,恢复到之前mark的位置处
        System.out.println("" + (char) buffer.get() + (char) buffer.get());
        printBuffer(buffer);

        buffer.reset(); // 使用mark()方法标记后, 可以使用reset()方法, 将指针回到mark标记的位置
        //buffer.rewind();

        printBuffer(buffer);

        buffer.compact();
        printBuffer(buffer);
        
        buffer.clear();
        printBuffer(buffer);
    }
    
    private static void printBuffer(Buffer buffer) {
        System.out.println("[limit=" + buffer.limit() 
                +", position = " + buffer.position()
                +", capacity = " + buffer.capacity()
                 +", array = " + buffer.toString()+"]");
    }
}

控制台输出:

[limit=10, position = 0, capacity = 10, array = java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]]
[limit=10, position = 5, capacity = 10, array = java.nio.HeapByteBuffer[pos=5 lim=10 cap=10]]
========切换到读取模式=======
[limit=5, position = 0, capacity = 10, array = java.nio.HeapByteBuffer[pos=0 lim=5 cap=10]]
He
[limit=5, position = 2, capacity = 10, array = java.nio.HeapByteBuffer[pos=2 lim=5 cap=10]]
[limit=5, position = 2, capacity = 10, array = java.nio.HeapByteBuffer[pos=2 lim=5 cap=10]]
ll
[limit=5, position = 4, capacity = 10, array = java.nio.HeapByteBuffer[pos=4 lim=5 cap=10]]
[limit=5, position = 2, capacity = 10, array = java.nio.HeapByteBuffer[pos=2 lim=5 cap=10]]
[limit=10, position = 3, capacity = 10, array = java.nio.HeapByteBuffer[pos=3 lim=10 cap=10]]
[limit=10, position = 0, capacity = 10, array = java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]]

1.4 Buffer复制 – 浅复制

  1. duplicate方法: 调用duplicate方法返回的Buffer对象就是复制了一份原始缓冲区,复制了position、limit、capacity这些属性(拥有独立的posittion,limit,capacity属性),但是复制后的缓冲区get和put所操作的数组还是与原始缓冲区一样的,所以对复制后的缓冲区进行修改也会修改原始的缓冲区
  2. asReadOnlyBuffer方法: 调用asReadOnlyBuffer方法返回的Buffer对象就是复制了一份原始缓冲区,复制了position、limit、capacity这些属性, 但是复制后的buffer对象只可读, 不能对底层数据进行修改
  3. slice方法:  返回一个新的buffer对象,这个新buffer和老buffer公用一个内存。但是被start和end索引偏移缩减了; (比如,一个buffer里有1到10个字节,我们只想要4-8个字节,就可以用这个函数buf.slice(4,8),因为他们共用一个内存,所以不会消耗内存) 因为共用内存,所以修改新的buffer后,老buffer的内容同样也会被修改;  buffer.slice([start], [end])

slice()方法使用示例:

public class SliceBuffer{
  static public void main( String args[] ) throws Exception {
    ByteBuffer buffer = ByteBuffer.allocate( 10 ); //创建一个buffer
    for (int i=0; i<buffer.capacity(); ++i) {
      buffer.put( (byte)i );  //向buffer中写入值
    }
    buffer.position(3); //将buffer的position属性设为3,limit属性设为7, 在后面循环中就只能操作[3,7)之间的内容
    buffer.limit(7);   
    ByteBuffer slice = buffer.slice();//使用slice()进行复制
    for (int i=0; i<slice.capacity(); ++i) {
      byte b = slice.get( i );
      b *= 11;
      slice.put( i, b ); //修改数据
    }
    buffer.position( 0 ); //将buffer的position属性设为0,limit属性设为10,以便输出所有内容
    buffer.limit( buffer.capacity() );
    while (buffer.remaining()>0) {
      System.out.println( buffer.get() );
    }
  }
}

控制台输出: 

0
1
2
33
44
55
66
7
8
9

public class DuplicateBuffer {
    public static void main(String[] args) {
        CharBuffer buffer = CharBuffer.allocate(8);  //创建buffer对象
        for(int i= 0 ; i < buffer.capacity() ; i++) {
            buffer.put(String.valueOf(i).charAt(0));
        }
        printBuffer(buffer);

        buffer.flip();  //切换到读模式
        printBuffer(buffer);
        
         buffer.position(3).limit(6).mark().position(5);
         printBuffer(buffer);

        CharBuffer dupeBuffer = buffer.duplicate();
        buffer.clear();
         printBuffer(buffer);
         
        printBuffer(dupeBuffer);
        
         dupeBuffer.clear();
        printBuffer(dupeBuffer);    
    }
    private static void printBuffer(Buffer buffer) {
        System.out.println("[limit=" + buffer.limit() 
                +", position = " + buffer.position()
                +", capacity = " + buffer.capacity()
                +", array = " + buffer.toString()+"]");
    }
}

使用示例:

[limit=8, position = 8, capacity = 8, array = ]
[limit=8, position = 0, capacity = 8, array = 01234567]
[limit=6, position = 5, capacity = 8, array = 5]
[limit=8, position = 0, capacity = 8, array = 01234567]
[limit=6, position = 5, capacity = 8, array = 5]
[limit=8, position = 0, capacity = 8, array = 01234567]

2. Channel

所有的 NIO 操作始于通道,通道是数据来源或数据写入的目的地,主要地(Channel可以比作水管, buffer可以比作水池, 读写操作可以看做是进水放水操作); java.nio 包中主要实现的以下几个 Channel:

  • FileChannel:文件通道,用于文件的读和写
  • DatagramChannel:用于 UDP 连接的接收和发送
  • SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端
  • ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求

使用示例:

public class CopyFile {
    static public void main(String args[]) throws Exception {
        String infile = "src/main/resources/CopyFile.java";
        String outfile = "src/main/resources/CopyFile.java.copy";
        // 从流中获取通道
        FileInputStream fin = new FileInputStream(infile);
        FileOutputStream fout = new FileOutputStream(outfile);

        FileChannel fcin = fin.getChannel();
        FileChannel fcout = fout.getChannel();

        // 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            // 读入之前要清空
            buffer.clear();
            // position自动前进
            int r = fcin.read(buffer);
            if (r == -1) {
                break;
            }
            // position = 0; limit=读到的字节数
            buffer.flip();
            // 从 buffer 中读
            fcout.write(buffer);
        }
    }
}

3. Selector

SelectorJava NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读、可写; 可以实现通过单线程管理多个channel,也就是可以管理多个网络链接


 

1. 创建Selector
Selector selector = Selector.open();
2. 注册Channel到Selector上
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

register的第二个参数,这个参数是一个“关注集合”,代表关注的channel状态,
有四种基础类型可供监听, 用SelectionKey中的常量表示如下(SelectionKey表示Selector和被注册的channel之间关系,保存channel感兴趣的事件)
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

3. 从Selector中选择channel; 一旦向Selector注册了一个或多个channel后,就可以调用select来获取channel; select方法会返回所有处于就绪状态的channel

select方法具体如下:
int select()
int select(long timeout)
int selectNow()
select()方法的返回值是一个int,代表有多少channel处于就绪了。也就是自上一次select后有多少channel进入就绪

selectedKeys(): 在调用select并返回了有channel就绪之后,可以通过选中的key集合来获取channel,这个操作通过调用selectedKeys()方法: Set<SelectionKey> selectedKeys = selector.selectedKeys();  

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove();
}

 NIO的优点:

1. 事件驱动模型

  • 避免多线程
  • 单线程处理多任务

2. 非阻塞IO,IO读写不再阻塞,而是返回0
3. 基于block的传输,通常比基于流的传输更高效
4. 更高级的IO函数,zero-copy
5. IO多路复用大大提高了java网络应用的可伸缩性和实用性

4. 测试示例(单线程模式)

服务器类:

package com.lic.demo.nio.demo;
import java.io.IOException;
public class NIOEchoServer {
    public static void main(String[] args) throws IOException {
    int port = 8080;
    if (args != null && args.length > 0) {
        try {
        port = Integer.valueOf(args[0]);
        } catch (NumberFormatException e) {
        // 采用默认值
        }
    }
    EchoHandler timeServer = new EchoHandler(port);
    new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
    }
}

服务端处理器类:

package com.lic.demo.nio.demo;
import java.io.EOFException;
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;
public class EchoHandler implements Runnable {
    private Selector selector;
    private ServerSocketChannel servChannel;
    private volatile boolean stop;
    private int num = 0;
    public EchoHandler(int port) {
        try {
            selector = Selector.open();  //创建selector
            /**
             * ServerSocketChannel是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。
             */
            servChannel = ServerSocketChannel.open();
            servChannel.configureBlocking(false);
            servChannel.socket().bind(new InetSocketAddress(port), 1024); //端口绑定
            /**
             * 是否有请求到达也交由selector去处理, 如果有新的请求到达, 则可以使用servChannel.accept()获取该方法将会返回一个SocketChannel
             * 将ServerSocketChannel注册到selector中去检测, 每次selector去轮询检测通道状态时接受到一个请求信息, 然后注册到selector中; 
             */
            servChannel.register(selector, SelectionKey.OP_ACCEPT); //注册通道
            System.out.println("服务器在端口[" + port + "]等待客户请求......");
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop() {
        this.stop = true;
    }

    @Override
    public void run() {
        while (!stop) {
            try {
                selector.select(1000);
                Set<SelectionKey> selectedKeys = selector.selectedKeys();  //获取到所有的SelectionKey, 遍历进行检测是否可操作
                Iterator<SelectionKey> it = selectedKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        handleInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null)
                                key.channel().close();
                        }
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }

        // 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
        if (selector != null)
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }

    private void handleInput(SelectionKey key) throws IOException {

        if (key.isValid()) {
            /**
             * 每一次selector都会检测ServerSocketChannel通道的状态,如果有新的请求信息则处理新接入的请求消息
             */
            if (key.isAcceptable()) {  
                /**
                 * ServerSocketChannel:selector负责检测该通道的状态, 一旦状态为OP_ACCEPT
                 * 则可以通过ServerSocketChannel获取客户端的连接通道SocketChannel;
                 * 调用该通道的accept()接收请求, 并将SocketChannel注册到selector中
                 */
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel socketChannel = ssc.accept(); // Non blocking, never null
                socketChannel.configureBlocking(false);
                SelectionKey sk = socketChannel.register(selector, SelectionKey.OP_READ);

                sk.attach(num++);
            }
            /**
             * 处理客户端的请求信息进行读取
             */
            if (key.isReadable()) {
                // 读取数据
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);  //创建buffer, 准备进行数据读取
                int readBytes = sc.read(readBuffer);  //读取数据
                if (readBytes > 0) {
                    readBuffer.flip(); //装换为写模式, 将buffer中的信息输出到控制台上
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes, "UTF-8");
                    System.out.println("来自客户端[" + key.attachment() + "]的输入: [" + body.trim() + "]!");

                    if (body.trim().equals("Quit")) {
                        System.out.println("关闭与客户端[" + key.attachment() + "]......");
                        key.cancel();
                        sc.close();
                    } else {
                        String response = "来自服务器端的响应:" + body;
                        doWrite(sc, response);
                    }

                } else if (readBytes < 0) {
                    // 对端链路关闭
                    key.cancel();
                    sc.close();
                } else {

                }
            }
        }
    }

    private void doWrite(SocketChannel channel, String response) throws IOException {
        if (response != null && response.trim().length() > 0) {
            byte[] bytes = response.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            writeBuffer.put(bytes);
            writeBuffer.flip();
            channel.write(writeBuffer);
        }
    }
}

客户端类:

package com.lic.demo.nio.demo;
public class NIOEchoClient {
    public static void main(String[] args) {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
            }
        }
        new Thread(new NIOEchoClientHandler("127.0.0.1", port), "NIOEchoClient-001").start();
    }
}

客户端处理器类:

package com.lic.demo.nio.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NIOEchoClientHandler implements Runnable {
    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private ExecutorService executorService;
    private volatile boolean stop;
    public NIOEchoClientHandler(String host, int port) {
        this.host = host == null ? "127.0.0.1" : host;
        this.port = port;
        this.executorService= Executors.newSingleThreadExecutor();
        try {
            selector = Selector.open();  //创建selector, 用于客户端监听channel的状态, 获取服务器端的信息
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
    @Override
    public void run() {
        try {
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            socketChannel.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while (!stop) {
            try {
                selector.select(1000);
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        handleInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null)
                                key.channel().close();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }

        // 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        if(executorService != null) {
            executorService.shutdown();
        }
    }

    private void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            // 判断是否连接成功
            SocketChannel sc = (SocketChannel) key.channel();
            if (key.isConnectable()) {
                if (sc.finishConnect()) {
                    System.out.println("连接到服务器......");
                    
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    System.out.println("请输入消息[输入\"Quit\"]退出:");

                    executorService.submit(() -> {
                        while(true) {
                            try {
                                buffer.clear();
                                InputStreamReader input = new InputStreamReader(System.in);
                                BufferedReader br = new BufferedReader(input);    
                                String msg = br.readLine();
                                if (msg.equals("Quit")) {
                                    System.out.println("关闭客户端......");
                                    key.cancel();
                                    sc.close();
                                    this.stop = true;
                                    break;
                                 }
                                buffer.put(msg.getBytes());
                                buffer.flip();
                                sc.write(buffer);
                                System.out.println("请输入消息[输入\"Quit\"]退出:");
                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }
                        }                        
                    });
                    sc.register(selector, SelectionKey.OP_READ);
                } else {
                    System.exit(1); // 连接失败,进程退出
                }
            }
            if (key.isReadable()) {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = sc.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes, "UTF-8");
                    System.out.println(body);
                    if(body.equals("Quit"))
                    {
                        this.stop = true;
                    }
                } else if (readBytes < 0) {
                    // 对端链路关闭
                    key.cancel();
                    sc.close();
                } else
                    ; // 读到0字节,忽略
            }
            
            if(key.isWritable()){
                 System.out.println("The key is writable");
            }
        }
    }

    private void doWrite(SocketChannel sc) throws IOException {
/*        System.out.println("请输入消息[输入\"Quit\"]退出:");
        BufferedReader stdIn = new BufferedReader(new InputStreamReader(
                System.in));
        String userInput;

        while ((userInput = stdIn.readLine()) != null) {
            out.println(userInput);
            System.out.println(in.readLine());*/
            byte[] req = "QUERY TIME ORDER".getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
            writeBuffer.put(req);
            writeBuffer.flip();
            sc.write(writeBuffer);
            if (!writeBuffer.hasRemaining())
                System.out.println("Send order 2 server succeed.");
           /*            
            if (userInput.equals("Quit")) {
                System.out.println("Closing client");
                out.close();
                in.close();
                stdIn.close();
                echoSocket.close();
                System.exit(1);
            }
            System.out.println("请输入消息[输入\"Quit\"]退出:");

        }*/
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值