java nio基本概念和入门和群聊客户端实例

首先我们先讨论一下nio和我们大家众所周知的bio的区别

java.io的核心是流(Stream)
,是面向流的编程,一个流只能进或者出****,没有有两种功能都有的流,既不存在可以进而且可以出的流。而且java.io是阻塞的,打一个比方把,我们现在的服务器要接收一张图片,而客户端发送的时候因为网络问题,传到一半就传不动了,而这时我们的服务器的read方法是一直阻塞等待客户端继续发送数据的,这种io模型下我们需要一个请求一个线程,当链接多的时候就需要经常的上下文切换,而且链接太多可能还会出现内存占用过多的现象。

java.nio 是面向块(block) 或者说是缓冲区(buffer)的编程,NIO有三个基本概念Selector ,Channel ,Buffer。

Buffer,底层其实就是数组,读和取都是buffer实现的。

数据读写永远不会发现直接访问Channel,必须通过buffer。

Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写,等状态。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。


首先我们讨论一下Buffer

直接上代码

public static void test1(){
        //分配长度为128的intbuffer
        IntBuffer intBuffer = IntBuffer.allocate(3);
        //随机的向IntBuffer添加数据
        for (int i = 0; i < intBuffer.capacity(); i++) {
            int i1 = new SecureRandom().nextInt(20);
            intBuffer.put(i1);
        }
        //翻转
        intBuffer.flip();
        //读
        while(intBuffer.hasRemaining()){
            System.out.println(intBuffer.get());
        }
    }

我们先定义了一个IntBuffer 然后随机的向IntBuffer 添加int类型数据,然后翻转,然后就开始读。
很多小伙伴会问,为什么还要翻转,才开始读,我不翻转可以吗,会发生什么事情。

让我们进入intBuffer.flip()这个方法一探究竟把

    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

A buffer’s capacity is the number of elements it contains. The
capacity of a buffer is never negative and never changes.

A buffer’s limit is the index of the first element that should
not be read or written. A buffer’s limit is never negative and is never
greater than its capacity.

A buffer’s position is the index of the next element to be
read or written. A buffer’s position is never negative and is never
greater than its limit.

这是java.nio.Buffer类上面的说明
大概意思就是

  • capacity 就是缓冲区的大小。初始化的时候也就是我们之前设定的IntBuffer.allocate(3),也就是3。

  • limit
    指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。初始化完它等于capacity

  • position
    下一个要被读或写的元素的索引,永远不会大于limit。

他们的大小关系是
0 <= position <= limit <= capacity

了解了这三个属性的意义之后我们来分析flip()这个方法
  • 首先我们向buffer写数据,每写一个数据position 会+XX,capacity保持不变,写完了之后我们要开始读,而position指向的是下一个要读的数据
  • 如果这时候我们直接开始读,下一个数据根本就不存在。所以我们需要将position归零
  • 在flip()里面把limit = position是为了标记,读写数据的时候的最后一位在哪里,这样才好判断我们再buffer里面写了多少数据读到哪里的时候停止

结论:读完数据要写数据,或者写完要读,中间必须调用flip()


Channel

上代码

 //写
    public static void test3()  {
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("noiotest3.txt");
            FileChannel channel = fileOutputStream.getChannel();

            ByteBuffer byteBuffer = ByteBuffer.allocate(128);
            byte[] msg = "hello nio !!!!".getBytes();

            for (int i = 0; i < msg.length; i++) {
                byteBuffer.put(msg[i]);
            }
//            将缓存字节数组的指针设置为数组的开始序列即数组下标0
            byteBuffer.flip();

            channel.write(byteBuffer);
            fileOutputStream.close();

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

    }
    //读
    public static void test2()  {
        try {
            FileInputStream fileInputStream = new FileInputStream("niotest1.txt");
            FileChannel channel = fileInputStream.getChannel();

            ByteBuffer byteBuffer = ByteBuffer.allocate(512);
            channel.read(byteBuffer);

            byteBuffer.flip();

            while (byteBuffer.hasRemaining()){
                byte b = byteBuffer.get();
                System.out.println("byte :  "+(char)b);
            }

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

直接上读文件和写文件的代码,开头还是我们熟悉的 FileOutputStream ,只不过我们从fileInputStream(fileOutputStream )里面获取了一个通道Channel,然后定义了一个buffer ,然后还是 flip(),然后开始读或者写,最后关闭。很简单的操作。
我们再上一个例子

public static void test4() throws Exception{
        FileInputStream inputStream = new FileInputStream("input.txt");
        FileOutputStream outputStream = new FileOutputStream("output.txt");

        FileChannel inputChannel = inputStream.getChannel();
        FileChannel outputChannel = outputStream.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(2);

        while (true){
            //重置position =0 limit=capacity
            //主要是为了下一次循环
            buffer.clear();
            //这里里面会调用buffer.put()
            int len = inputChannel.read(buffer);
            System.out.println(len);
            if (-1 == len){
                break;
            }
            //让position等于0然后好开始写
            buffer.flip();
            //这里会让limit和position一起移动
            outputChannel.write(buffer);
        }

        inputChannel.close();
        outputChannel.close();

    }

这个例子里面是上面的结合版,但是多了一个buffer.clear();
首先还是获取文件的channel,然后定义buffer,然后一个循环,开始读,如果读到数据的长度等于-1,就退出循环。最后还是要关闭。为什么要clear()呢?clear()究竟是起了什么作用,其实我注释上面已经写的很明白了,它是为了下一次循环。我们进入这个方法

public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

它也是操作了position,limit,capacity这三个属性。它把position,和limit都初始化到我们的buffer刚刚创建的时候。

如果我们不buffer.clear();会发生什么事情呢?

1、第一次循环:
  • 调用inputChannel.read(buffer);把buffer写满
  • buffer.flip();开始outputChannel.write(buffer);正常的写数据。
2、第二次循环:
  • 调用inputChannel.read(buffer);因为position==limit 所有不可能再往buffer写了,因为position不能大于limit,所以数据进不了buffer,
  • 然后buffer.flip() 把之前的数据写到文件里面。

这样就造成了一个问题,数据永远读不完,就成了死循环,所以我们循环读数据的时候一定要记住buffer.clear()

现在我们来讲一个netty一直吹的概念:零拷贝

 public static void test1() throws Exception{
        FileInputStream inputStream = new FileInputStream("input2.txt");
        FileOutputStream outputStream = new FileOutputStream("output2.txt");

        FileChannel inputChannel = inputStream.getChannel();
        FileChannel outputChannel = outputStream.getChannel();

        ByteBuffer buffer = ByteBuffer.allocateDirect(2);

        while (true){
            //重置position =0 limit=capacity
            //主要是为了下一次循环起作用
            buffer.clear();
            //这里里面会调用buffer.put()
            int len = inputChannel.read(buffer);
            System.out.println(len);
            if (-1 == len){
                break;
            }
            //让position等于0然后好开始写
            buffer.flip();
            //这里会让limit和position一起移动
            outputChannel.write(buffer);
        }

        inputChannel.close();
        outputChannel.close();
    }

细心的同学可能看到了创建buffer的方式改变了,从 allocate(2) 变成了allocateDirect(2),这有啥区别呢Direct字面意思是直接的,大概就是分配直接内存的buffer。
其实是这两个创建buffer位置有不,allocate是在java堆上面创建,可以理解为在jvm里面创建的,而直接在堆外内存上面创建buffer。

  • allocate是在JVM里面创建的对象
  • allocateDirect是在操作系统的内存上面创建的对象

为啥要这样呢,因为jvm不能直接从操作系统的io直接获得数据传输通道,需要先把数据从网络读到操作系统的内存再读到JVM里面,这样就造成了内存拷贝。少量的数据可能没啥感觉,但是数据多了肯定是有很大影响的。

allocateDirect是在JVM外面的内存创建了一个buffer,在
++java.nio.Buffer++里面有一个++long address++属性我们可以把这个address属性理解为它直接指向了堆外内存的buffer,直接和堆外内存交互。

还有一个问题: 为什么操作系统不直接访问java内存,这样多简单啊,还装这么多的X

我们都知道JVM会自动的帮我们管理内存,就比如说分代回收吧,它分年轻代,存活代,老年代,gc的时候会移动对象,内存地址会变,如果这时候native正在操作数据的时候gc会乱套

再来几个例子
创建分片buffer
 /** slice buffer 与 原来的buffer共享底层数组
     * @author guoyitao
     * @date 2019/3/18 21:07
     * @params
     * @return 
     */
    public static void test1SliceBuffer(){
        ByteBuffer buffer = ByteBuffer.allocate(10);

        for (int i = 0; i <buffer.capacity() ; i++) {
            buffer.put((byte) i);
        }

        buffer.position(2);
        buffer.limit(6);
        //分片,并不是复制出来的
        ByteBuffer slice = buffer.slice();

        for (int i = 0; i < slice.capacity(); i++) {
            byte b = slice.get(i);
            b *=2;
            slice.put(i, b);
        }

        buffer.clear();

        while (buffer.hasRemaining()){
            System.out.println(buffer.get());
        }
    }

创建只读buffer

 /** 创建只读buffer
     * @author guoyitao
     * @date 2019/3/18 21:22
     * @params
     * @return
     */
    public static void testOnlyBuffer(){
        ByteBuffer buffer = ByteBuffer.allocate(10);

        for (int i = 0; i <buffer.capacity() ; i++) {
            buffer.put((byte) i);
        }

        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();

        System.out.println(buffer.getClass());
        System.out.println(readOnlyBuffer.getClass());
        readOnlyBuffer.put("as".getBytes());
    }

关于NIO网络编程

buffer的Scattering和Gathering
public static void main(String[] args)throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress address = new InetSocketAddress(8899);
        serverSocketChannel.socket().bind(address);

        int messagelen = 2 + 3 + 4;
        ByteBuffer[] buffers = new ByteBuffer[3];

        buffers[0] = ByteBuffer.allocate(2);
        buffers[1] = ByteBuffer.allocate(3);
        buffers[2] = ByteBuffer.allocate(4);

        SocketChannel socketChannel = serverSocketChannel.accept();

        while (true){

            int bytesRead = 0;

            while (bytesRead < messagelen){
            //Scattering
                long read = socketChannel.read(buffers);
                bytesRead += read;

                System.out.println("bytesread: " + bytesRead);

                Arrays.asList(buffers).stream().map(
                        buffer -> "position:" +buffer.position() + ",limit: " + buffer.limit()).
                        forEach(System.out::println);

            }

            Arrays.asList(buffers).forEach(byteBuffer -> {
                byteBuffer.flip();
            });

            long bytesWritten = 0;

            while (bytesWritten < messagelen){
            //Gathering
                long l = socketChannel.write(buffers);
                bytesWritten += l;
            }

            Arrays.asList(buffers).forEach(byteBuffer -> {
                byteBuffer.clear();
            });

            System.out.println("bytesRead: "+ bytesRead + ",bytesWritten " + bytesWritten + ",messageLength" + messagelen);
        }

    }

++关于niosocket的我们先不看++,先只看我//Gathering//Scattering的;两个地方

  • Scattering 把来自一个channel的数据读到多个buffer,按照顺序,读满第一个再第二个。。。。。。。
  • Gathering 把多个buffer写到channel,按照顺序,写完第一个再第二个

再上代码

public static void test1() throws IOException, InterruptedException {
        int[] prots = new int[5];
        prots[0] = 5000;
        prots[1] = 5001;
        prots[2] = 5002;
        prots[3] = 5003;
        prots[4] = 5004;

        Selector selector = Selector.open();

//        System.out.println(SelectorProvider.provider().getClass());
//        System.out.println(sun.nio.ch.DefaultSelectorProvider.create().getClass());

        for (int i = 0; i < prots.length; i++) {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            ServerSocket socket = serverSocketChannel.socket();
            InetSocketAddress address = new InetSocketAddress(prots[i]);
            socket.bind(address);

            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("监听端口" + prots[i]);
        }

        while (true){

            int select = selector.select();
            System.out.println("number :" + select);

            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            System.out.println("selectionKeys: " + selectionKeys);

            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                //连接事件
                if (selectionKey.isAcceptable()){
                    ServerSocketChannel serverSocketChannel  = (ServerSocketChannel) selectionKey.channel();

                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector,SelectionKey.OP_READ);

                    iterator.remove();

                    System.out.println("获得客户端连接: " + socketChannel);
                 //读事件
                } else if (selectionKey.isReadable()){
                    SocketChannel socketChannel  = (SocketChannel) selectionKey.channel();

                    int byteRead = 0;
                    while (true){
                        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                        byteBuffer.clear();

                        int read = socketChannel.read(byteBuffer);
                        if (read <= 0){
                            break;
                        }

                        byteBuffer.flip();

                        socketChannel.write(byteBuffer);

                        byteRead += read;
                    }

                    System.out.println("读取了:" + byteRead  + ",来自" + socketChannel);

                    iterator.remove();
                }


            }


        }
    }

现在我们开始讲解nio网络编程的helloworld

selectedKey将Channel与Selector建立了关系,并维护了channel事件

可以通过cancel方法取消键,取消的键不会立即从selector中移除,而是添加到cancelledKeys中,在下一次select操作时移除它,所以在调用某个key时,最好使用isValid进行校验。

一些事件类型
OP_ACCEPT:连接可接受操作,仅ServerSocketChannel支持
OP_CONNECT:连接操作,Client端支持的一种操作
OP_READ/OP_WRITE 读和写时间

一些方法介绍:

  • public abstract SelectableChannel channel():返回此选择键所关联的通道.即使此key已经被取消,仍然会返回.

  • public abstract Selector selector():返回此选择键所关联的选择器,即使此键已经被取消,仍然会返回.

  • public abstract boolean isValid():检测此key是否有效.当key被取消,或者通道被关闭,或者selector被关闭,都将导致此key无效.在AbstractSelector.removeKey(key)中,会导致selectionKey被置为无效.

  • public abstract void cancel():请求将此键取消注册.一旦返回成功,那么该键就是无效的,被添加到selector的cancelledKeys中.cancel操作将key的valid属性置为false,并执行selector.cancel(key)(即将key加入cancelledkey集合)

  • public abstract int interesOps():获得此键的interes集合.

  • public abstract SelectionKey interestOps(int ops):将此键的interst设置为指定值.此操作会对ops和channel.validOps进行校验.如果此ops不会当前channel支持,将抛出异常.

  • public abstract int readyOps():获取此键上ready操作集合.即在当前通道上已经就绪的事件.

  • public final boolean isReadable(): 检测此键是否为"read"事件.等效于:k.,readyOps() & OP_READ != 0;还有isWritable(),isConnectable(),isAcceptable()

  • public final Object attach(Object ob):将给定的对象作为附件添加到此key上.在key有效期间,附件可以在多个ops事件中传递.

  • public final Object attachment():获取附件.一个channel的附件,可以再当前Channel(或者说是SelectionKey)生命周期中共享,但是attachment数据不会作为socket数据在网络中传输.

  • 首先定义了了一个prots里面存放了端口号

  • 创建一个Selector

  • 循环的创建对个ServerSocketChannel,并将configureBlocking(false)记住这里必须是false,设置为非阻塞。

  • 然后将刚才定义的端口绑定到socket里面,在把serverSocketChannel,注册到Selector里面

  • 开始一个死循环,
    服务器一起的就会阻塞在selector.select()一直检查通道,直到通道的状态发送变化,如果发送变化会返回selectedKeys的个数

  • 获得selectionKeys 遍历它,if判断selectionKey是什么事件,并作出相应的逻辑

  • 这里必须重新注册到selector,然后把事件设置为OP_ACCEPT之后的下一个想处理的事件,然后iterator.remove()

要知道,一码事归一码事,channel是注册在selector中的,在后面的轮询中,是先将已准备好的channel挑选出来,即selector.select(),再通过selectedKeys()生成的一个SelectionKey迭代器进行轮询的,一次轮询会将这个迭代器中的每个SelectionKey都遍历一遍,每次访问后都remove()相应的SelectionKey,但是移除了selectedKeys中的SelectionKey不代表移除了selector中的channel信息(这点很重要),注册过的channel信息会以SelectionKey的形式存储在selector.keys()中,也就是说每次select()后的selectedKeys迭代器中是不能还有成员的,但keys()中的成员是不会被删除的(以此来记录channel信息)。

那么为什么要删除呢,要知道,迭代器如果只需要访问的话,直接访问就好了,完全没必要remove()其中的元素啊,查询了相关资料,一致的回答是为了防止重复处理,后来又有信息说明:每次循环调用remove()是因为selector不会自己从已选择集合中移除selectionKey实例,必须在处理完通道时自己移除,这样,在下次select时,会将这个就绪通道添加到已选择通道集合中,其实到这里就已经可以理解了,selector不会自己删除selectedKeys()集合中的selectionKey,那么如果不人工remove(),将导致下次select()的时候selectedKeys()中仍有上次轮询留下来的信息,这样必然会出现错误,假设这次轮询时该通道并没有准备好,却又由于上次轮询未被remove()的原因被认为已经准备好了,这样能不出错吗?

即selector.select()会将准备好的channel以SelectionKey的形式放置于selector的selectedKeys()中供使用者迭代,使用的过程中需将selectedKeys清空,这样下次selector.select()时就不会出现错误了。


下面再来最后一个demo

聊天客户端和服务端 群聊()

Server
/**
 *
 * remove SelectKey是为了防止下一次select的时候重复处理
 * @Description: 群聊服务器
 * @Author: guo
 * @CreateDate: 2019/3/19
 * @UpdateUser:
 */
public class NIOServer {
    public static final int serverPort = 5001;
    public static final Map<String, SocketChannel> clientMap = new ConcurrentHashMap<>();

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //非阻塞
        serverSocketChannel.configureBlocking(false);
        //服务器接套字
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(serverPort));
        //注册准备接收事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器端口: " + serverPort);


        while (true){
            //等待事件
            selector.select();
            //获取事件token
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                //读取事件
                SelectionKey selectionKey = iterator.next();

                //处理事件
                if (selectionKey.isAcceptable() && selectionKey.isValid()){
                    ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                    //接受与此通道的套接字建立的连接, 这是用户连接!!!!!!!!!
                    SocketChannel client = server.accept();
                    //非阻塞
                    client.configureBlocking(false);
                    //连接成功准备接受读事件
                    client.register(selector,SelectionKey.OP_READ);
                    //保存连接用户
                    clientMap.put(UUID.randomUUID().toString(),client);

                    iterator.remove();
                }else if (selectionKey.isReadable()&& selectionKey.isValid()){
                    SocketChannel client = (SocketChannel) selectionKey.channel();

                    //接受数据
                    try {
                        while (true){
                            ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);
                            readBuffer.clear();

                            //来自哪个用户
                            String fromKey = null;
                            for (Map.Entry<String, SocketChannel> channelEntry : clientMap.entrySet()) {
                                if (channelEntry.getValue() == client){
                                    fromKey = channelEntry.getKey();
                                    break;
                                }
                            }

                            int read = 0;

                            try {
                                read = client.read(readBuffer);
                            } catch (IOException e) {
                                /*
                                * 客户端异常关闭处理 java.io.IOException: 远程主机强迫关闭了一个现有的连接。
                                * */
                                client.close();
                                selectionKey.cancel();
                                e.printStackTrace();

                                /*
                                * 必须把clientMap原来的SocketChannel剔除掉,因为原来的SocketChannel已经关闭
                                * 如果不剔除可能抛出java.nio.channels.ClosedChannelException
                                * */
                                clientMap.remove(fromKey);
                                break;
                            }
                            if (read == -1){
                                client.close();
                                selectionKey.cancel();
                                clientMap.remove(fromKey);
                                break;
                            }else if(read == 0){
                                break;
                            }

                            readBuffer.flip();

                            //转码取值
                            Charset charset = Charset.forName("utf-8");
                            String receiveData = String.valueOf(charset.decode(readBuffer).array());

                            //发送
                            System.out.println("服务器收到" + receiveData + ",来自:" + client);
                            for (Map.Entry<String, SocketChannel> channelEntry : clientMap.entrySet()) {
                                SocketChannel value = channelEntry.getValue();
                                ByteBuffer writeBuffer = ByteBuffer.allocateDirect(1024);

                                writeBuffer.put((fromKey + receiveData).getBytes());
                                writeBuffer.flip();

                                value.write(writeBuffer);
                            }
                            iterator.remove();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();

                    }


                }
            }


        }

    }
}

Client

/**
 * @Description: 群聊客户端
 * @Author: guo
 * @CreateDate: 2019/3/19
 * @UpdateUser:
 */
public class NIOClient {
    public static void main(String[] args) throws IOException {
        //创建一个SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        //非阻塞
        socketChannel.configureBlocking(false);
        //创建选择器
        Selector selector = Selector.open();
        //注册 设置事件为 链接
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        //链接到服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1",NIOServer.serverPort));

        while (true){
            //等待事件
            selector.select();
            //获取事件token
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                //读取事件
                SelectionKey selectionKey = iterator.next();
                //处理事件 判断是不是OP_CONNECT事件
                if (selectionKey.isConnectable()) {
                    //获得SocketChannel
                    SocketChannel client = (SocketChannel) selectionKey.channel();
//                    建立连接
                    if (client.isConnectionPending()) {
                        //完成链接
                        client.finishConnect();

                        ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                        writeBuffer.put((LocalDateTime.now() + "连接成功").getBytes());
                        writeBuffer.flip();
                        //发送链接成功信息
                        client.write(writeBuffer);
                        //创建一个单个线程的线程池
                        ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
                        executorService.submit(() -> {
                            while (true) {
                                try {
                                    //这里之前讲过
                                    writeBuffer.clear();
                                    //标准输入,从键盘输入
                                    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                                    String line = reader.readLine();

                                    writeBuffer.put(line.getBytes());
                                    writeBuffer.flip();
                                    //从客户端向服务端发送数据
                                    client.write(writeBuffer);
                                } catch (IOException e) {
                                    e.printStackTrace();

                                }
                            }
                        });

                    }

                    client.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    //收数据
                    SocketChannel client = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

                    int read = client.read(byteBuffer);
                    if (read > 0) {

                        String receiveData = new String(byteBuffer.array(),0,read);
                        System.out.println("收到消息: " + receiveData);
                    }

                }
            }
            //之前讲过
            selectionKeys.clear();
        }

    }
}

java NIO其实就是多路复用IO模型的实现,多人聊天程序就是很经典的一个例子,服务器可以一个线程处理多个用户的链接,并完成一些业务操作,但是用气来的真的太复杂,虽然博主没有用01010101写过代码,但是我感觉用java nio原生api写代码真的是要人命,所以博主去学习netty啦,

拜拜,下次见

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值