Netty(一)基础socketchannel,Buffer,selector黏包 半包解决 实战

NIO 基础

non-blockiong io:非阻塞

阻塞vs非阻塞
在这里插入图片描述

三大组件

1.channel & Buffer

channel : 双向流
Buffer:暂存数据 缓冲区
在这里插入图片描述
常见channel:

  • List item
  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

常见的buffer

  • ByteBuffer
  • MappedByteBuffer
  • DirectByteBuffer
  • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer
  • 在这里插入图片描述

Selector

在这里插入图片描述

多线程版服务器设计

  • 内存占用过高,来一个人就要开辟一个线程
  • 线程上下文切换成本高 ,cpu上面能承载的线程是有限度的
    在这里插入图片描述

线程池班的设计
阻塞
阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作。被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到条件满足。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iw4czSpD-1611297346687)(C:\Users\123\Pictures\Markdown\image-20201228213409948.png)]

非阻塞
非阻塞操作是指在进行设备操作是,若操作条件不满足并不会挂起,而是直接返回或重新查询(一直占用CPU资源)直到操作条件满足为止。
————————————————
版权声明:本文为CSDN博主「几盒猫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_46216143/article/details/112984526
在这里插入图片描述

  • 阻塞模式下,一个线程在同一时间只能处理一个socket
  • 仅适合短链接场景

Selector 版设计
在这里插入图片描述
selector 的作用就是配合一个线程来管理多个 channel(fileChannel因为是阻塞式的,所以无法使用selector),获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下,当一个channel中没有执行任务时,可以去执行其他channel中的任务。适合连接数多,但流量较少的场景
线程的利用率得到提升 。

bytebuffer结构

一开始是空的 写完,position的移动类似于栈 先写入数据再 ++ ,一开始posiotion再0号位置
在这里插入图片描述
切换写模式直接重置指针 ,limit是限制
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

channel 和 读写案列

将读取的数据写入channel 然后放入缓冲区 buffer ,在从buffer 取出字节
在这里插入图片描述
向buffer写数据:1.调用channel的read (例如下面的例子)
2.调用buffer的put方法 例如buf.put((byte)127)

@Test
    void contextLoads() {
        //FileChannel 1.输入流 输出流  2.R

        try (FileChannel channel = new FileInputStream(("src/main/resources/data.txt")).getChannel()){

            //设置缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(3);
            //读取数据

            while (channel.read(buffer)!=-1){
                //打印

                buffer.flip() ;//切换成读模式
                while (buffer.hasRemaining()){ //看是否还有剩余
                    byte b = buffer.get();
                    // log.info("读取到的字节:{}",(char)b);
                    System.out.print((char) b);
                }

                buffer.clear() ; //切换到写模式
            }


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

bytebuffer使用

在这里插入图片描述
读取会自动转化为十进制
在这里插入图片描述
数据前移并不会删除 position所指向的3号位置 :64,会出现两个64
但是下次写还是会从 position3号位置开始写 直接覆盖64

allocate和allocateDirect
  • 放入堆内存会收到gc 影响,读写效率低下 收到gc的影响
  • 放入直接内存 读写效率高(少一次拷贝) 不受到gc影响
  • netty 对此都有提高
    在这里插入图片描述
mark & reset
   @Test
    public void test2(){
        ByteBuffer buf = ByteBuffer.allocate(10);
        buf.put(new byte[]{'a','b','c','d'});
        buf.flip(); // 切换成读模式

        //position位置跳到 5
        buf.get(new byte[4]);

        //重复读 position位置:0
        buf.rewind();

        byte b = buf.get();
        System.out.println((char) b); //p:0->1
        byte b2 = buf.get();
        System.out.println((char) b2);//p:1->2
        //mark & reset
        //mark 作一个标记 ,记录position的位置 , 然后读 ,然后reset 将position位置重置到标记位
        buf.mark(); //p: 2
        byte b3 = buf.get();
        System.out.println((char) b3); //p:2->3
        byte b4 = buf.get();
        System.out.println((char) b4); //p:3->4

        buf.reset();
        byte b5 = buf.get();
        System.out.println((char) b5); //p:2->3
        byte b6 = buf.get();
        System.out.println((char) b6);//p:3->4
    }

在这里插入图片描述

字符串《 —----互相转化——》byth

  @Test
    public void test3(){

        //1.字符串转化为 byteBuffer
        ByteBuffer buf = ByteBuffer.allocate(15);
        buf.put("hello".getBytes());

         //2. charset
        ByteBuffer hello = StandardCharsets.UTF_8.encode("hello");

        //3.wrap
        ByteBuffer buffer = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));

    }

在这里插入图片描述

半包 ,黏包

 @Test
    public void tes4(){
        ByteBuffer buffer = ByteBuffer.allocate(330);
        //黏包  三条数据在一起发送以\n 发送
        buffer.put("hello\n nihao\nchengkuan".getBytes());

        split(buffer) ;
        
        //半包: 数据不全 
        buffer.put(" xihuan ni \n".getBytes());
        split(buffer) ;
    }

    public static void split(ByteBuffer buffer){
        buffer.flip() ;
        for (int i =0;i< buffer.limit();i++){

            if (buffer.get(i)=='\n' ){
                int length = i+1-buffer.position();
                ByteBuffer source = ByteBuffer.allocate(length+1);
             for ( int k = 0;k <length; k++){
                 byte out = buffer.get(k);
                 System.out.print((char) out);
             }

            }
        }

    }

FileChannel:文件拷贝

# 传输效率高  最高2G 
#可以多次传输
    @Test
    public void tes5(){
       try(
               FileChannel from = new FileInputStream("src/main/resources/data.txt").getChannel();
               FileChannel to =   new FileOutputStream("src/main/resources/to.txt").getChannel();
       ){
         from.transferTo(0,from.size(),to);


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


    }


改进

 @Test
    public void tes5(){
       try(
               FileChannel from = new FileInputStream("src/main/resources/data.txt").getChannel();
               FileChannel to =   new FileOutputStream("src/main/resources/to.txt").getChannel();
       ){
           long size = from.size();

         //left 还剩余多少字节没有传输
         for (long left = from.size();left>0;){

             //transferTo一次只能写2g  假设4G 第一次:写了2G 第二次:from.transferTo(4-2, 2, to);
             // size-left :从上一次位置开始获取值 left:一共要写多少数据
             left -= from.transferTo(size-left, left, to);
         }

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

遍历多级目录


    @Test
    public void tes2() throws IOException {

        //在匿名内部内种只能用final类型的变量 ,故此处计数器采用AtomicInteger
        AtomicInteger dirCount = new AtomicInteger();
        AtomicInteger fileCount = new AtomicInteger();

        Files.walkFileTree(Paths.get("src/main"),new SimpleFileVisitor<Path>(){

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                System.out.println("====>"+dir);
                dirCount.incrementAndGet() ;
                return super.preVisitDirectory(dir, attrs);
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println("文件夹是: "+file);
                return super.visitFile(file, attrs);
            }


        });
        System.out.println(dirCount);

    }

在这里插入图片描述

Files文件拷贝

@Test
    public void tes6() throws IOException {
        String source = "E:\\yuanmashidai\\project\\nettytest01\\src\\main\\resources";

        //D 换成 E 会造成循环递归
        String dis = "D:\\yuanmashidai\\project\\nettytest01\\src\\main\\resources\\to";

        Files.walk(Paths.get(source)).forEach(path -> {
            //扫到了 src/main/resources/data.txt -》src/main/resources/to/data.txt
            String tar = path.toString().replace(source, dis);

            if (Files.isDirectory(path)){
                try {
                    Files.createDirectories(Paths.get(tar));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (Files.isRegularFile(path)){
                try {
                    Files.copy(path,Paths.get(tar));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


        });
    }

阻塞模式

服务器端

//阻塞 :线程没法读到数据的时候 就会停止运行 
    public static void main(String[] args) throws IOException {
        //用户发的数据
        ByteBuffer buff = ByteBuffer.allocate(16);
        //nio 理解阻塞
        //建立服务器
        log.info("connect ....");
        ServerSocketChannel ssc = ServerSocketChannel.open();

        //建立监听端口
        ssc.bind(new InetSocketAddress(8080)) ;


        //建立连接集合
        ArrayList<SocketChannel> channels = new ArrayList<>();
        while (true){
            //sc 与客户端进行通信
            SocketChannel sc = ssc.accept();
            log.info("connect ....{}",sc);//阻塞模式 连接建立以后才会运行
            channels.add(sc) ;
            for (SocketChannel channel : channels) {
                //读取数据 将数据读进buff
                channel.read(buff); //

                //buff切换到读模式
                buff.flip() ;  //buf也是阻塞的 ,再没有数据的时候不会执行大于
                log.info("readSource ....{}");
                System.out.println((char) buff.get());

                //再换成写模式 position 置 0
                buff.clear() ;
                log.info("clear ....{}");

            }


        }
    }


客户端

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

        //与服务器建立连接
        sc.connect(new InetSocketAddress("localhost",8080));

        System.out.println("waiting");

    }

将客户端改为非阻塞模式



    //阻塞 :线程没法读到数据的时候 就会停止运行
    public static void main(String[] args) throws IOException {
        //用户发的数据
        ByteBuffer buff = ByteBuffer.allocate(16);
        //nio 理解阻塞
        //建立服务器
        log.info("connect ....");
        ServerSocketChannel ssc = ServerSocketChannel.open();

        //将 客户端改为非阻塞模式
        ssc.configureBlocking(false);

        //建立监听端口
        ssc.bind(new InetSocketAddress(8080)) ;


        //建立连接集合
        ArrayList<SocketChannel> channels = new ArrayList<>();
        while (true){
            //sc 与客户端进行通信
            SocketChannel sc = ssc.accept();

            if (sc!=null){
                sc.configureBlocking(false) ; //连接管道设置为非阻塞
                log.info("connect ....{}",sc);//阻塞模式 连接建立以后才会运行
                channels.add(sc) ;
            }


            for (SocketChannel channel : channels) {
                //读取数据 将数据读进buff
                int read = channel.read(buff);// 未读到数据 read = 0,此时sc = channel
                                               // 当 sc开启非阻塞 ,channel不会报空指针,前提sc不为空

                //buff切换到读模式
                buff.flip() ;  //buf也是阻塞的 ,再没有数据的时候不会执行大于
              if(read!=0){
                  log.info("readSource ....{}");
              }
               // System.out.println((char) buff.get());

                //再换成写模式 position 置 0
                buff.clear() ;

                if(read!=0){
                    log.info("clear ....{}");
                }

            }


        }
    }

在这里插入图片描述

selector 模式


 - accept: 会在有连接请求的时候触发           serverSocketChannel.accept()    返回一个 SocketChannel
 - connect:是客户端 , 连接建立后触发 
 
 // selectionkey关注以下的 事件   sscKey.interestOps(SelectionKey.OP_ACCEPT)
 - read :可读事件
 - write :可写事件
事件发生后要么处理要么取消不能置之不理 

selector 就是用来监听事件的
selector 何时不阻塞

  1. 客户端发起请求触发 accept
    2.客户端发送数据,客户端正常 异常关闭 ,都会触发read事件,如果数据大于buffer缓冲区 会触发多次读取事件
    3.channel 可写,会触发write事件
    4.在linux发生nio bug时也会
    调用selector.wakeup
    调用seletor.close
    selector所在线程interrupt

@Slf4j
public class server02 {



    public static void main(String[] args) throws IOException, InterruptedException {

        //引入selecter :管理 多个channel !!!
        //1. 创建selector ,selector 会监听四种事件
        Selector selector = Selector.open();


        ByteBuffer buff = ByteBuffer.allocate(16);
        //nio 理解阻塞
        //建立服务器
        log.info("connect ....");

        //ssc 可以创建 channel
        ServerSocketChannel ssc = ServerSocketChannel.open();

        //将 客户端改为非阻塞模式
        ssc.configureBlocking(false);

        //2. 将  channel 注册进selector sscKey相当于一个管理员
        SelectionKey sscKey = ssc.register(selector, 0, null);
        log.debug("管理员是  :{}",sscKey);
        sscKey.interestOps(SelectionKey.OP_ACCEPT) ;

        //建立监听端口
        ssc.bind(new InetSocketAddress(8080)) ;

        //建立连接集合
        ArrayList<SocketChannel> channels = new ArrayList<>();
        while (true){
            //3. 没有事件线程阻塞 ,有事件发生 才会恢复向下运行

            log.info("遍历一下~~~");
         selector.select();

         //处理事件 ,selectionKeys 包含了所以可发生的事件 !
          //  Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            Thread.sleep(5000);

            while (iterator.hasNext()){
                log.info("遍历一下 SelectionKey ~~~");
                //SelectionKey :代表了每一个事件
                SelectionKey key = iterator.next();

                log.debug("key:{}",key);
                //4. 拿到 触发事件的channel  假设将下面的代码进行注释 ,则会发生
                //log.info("遍历一下~~~");无线循环 ,未处理的事件会重新加入队列
                ServerSocketChannel channel = (ServerSocketChannel) key.channel();

                //建立连接
                SocketChannel sc = channel.accept();
                log.debug("channel:{}",sc);
                
                //key.cancel:将事件取消
            }
        }
    }
}

让selector处理不同事件



    public static void main(String[] args) throws IOException, InterruptedException {

        //引入selecter :管理 多个channel !!!
        //1. 创建selector ,selector 会监听四种事件
        Selector selector = Selector.open();


        ByteBuffer buff = ByteBuffer.allocate(16);
        //nio 理解阻塞
        //建立服务器
        log.info("connect ....");

        //ssc 可以创建 channel
        ServerSocketChannel ssc = ServerSocketChannel.open();

        //将 客户端改为非阻塞模式
        ssc.configureBlocking(false);

        //2. 将  channel 注册进selector sscKey相当于一个管理员
        SelectionKey sscKey = ssc.register(selector, 0, null);
        log.debug("管理员是  :{}",sscKey);
        sscKey.interestOps(SelectionKey.OP_ACCEPT) ;

        //建立监听端口
        ssc.bind(new InetSocketAddress(8080)) ;

        //建立连接集合
        ArrayList<SocketChannel> channels = new ArrayList<>();
        while (true){
            //3. 没有事件线程阻塞 ,有事件发生 才会恢复向下运行

            log.info("遍历一下~~~");
         selector.select();

         //处理事件 ,selectionKeys 包含了所以可发生的事件 !
          //  Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            Thread.sleep(5000);

            while (iterator.hasNext()){
                log.info("遍历一下 SelectionKey ~~~");
                //SelectionKey :代表了每一个事件
                SelectionKey key = iterator.next();

                //区分事件类型
                if (key.isAcceptable()){
                    log.debug("is accept things ,key:{}",key);
                    //4. 拿到 触发事件的channel  假设将下面的代码进行注释 ,则会发生
                    //log.info("遍历一下~~~");无线循环 ,未处理的事件会重新加入队列
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    //建立连接
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false) ;

                    //将 socketchannel注册给selector   SelectionKey 《--- (sscKey 管理ServerSocketChannel)
                    //                                             《 -- (sc 管理Channel)
                    SelectionKey scKey = sc.register(selector, 0, null);

                    //设置关注事件类型
                    scKey.interestOps(SelectionKey.OP_READ) ;
                    log.debug("channel:{}",sc);
                }else if (key.isReadable()){
                    log.debug("is read things ,key:{}",key); //只有触发accetp 建立一个管道 注册进selecor 才能读取数据 
                    SocketChannel channel = (SocketChannel) key.channel();
                    //读取事件
                    ByteBuffer buffer = ByteBuffer.allocate(166);
                    //读取数据
                    channel.read(buff) ;
                    buffer.flip() ;// 切换读模式
                }

                //key.cancel:将事件取消
            }
        }
    }

为什么处理完一个事件需要删除

避免重复处理 。
添加链接描述

  1. ssc (serversocketChannel):监听 accept 注册进红框
  2. sc(socketChannel):监听读写 注册进红框
  3. 来了一个accept事件: ssc 进入绿框 处理事件 :建立了一个 sc (建立sockerchannel),然后删除事件
  4. 来了一个 read事件: ssc 进入绿框 处理事件 :建立了一个 sc (监听读写),然后删除事件

在这里插入图片描述


    public static void main(String[] args) throws IOException, InterruptedException {

        //引入selecter :管理 多个channel !!!
        //1. 创建selector ,selector 会监听四种事件
        Selector selector = Selector.open();


        ByteBuffer buff = ByteBuffer.allocate(16);
        //nio 理解阻塞
        //建立服务器
        log.info("connect ....");

        //ssc 可以创建 channel
        ServerSocketChannel ssc = ServerSocketChannel.open();

        //将 客户端改为非阻塞模式
        ssc.configureBlocking(false);

        //2. 将  channel 注册进selector sscKey相当于一个管理员
        SelectionKey sscKey = ssc.register(selector, 0, null);
        log.debug("管理员是  :{}",sscKey);
        sscKey.interestOps(SelectionKey.OP_ACCEPT) ;

        //建立监听端口
        ssc.bind(new InetSocketAddress(8080)) ;

        //建立连接集合
        ArrayList<SocketChannel> channels = new ArrayList<>();
        while (true){
            //3. 没有事件线程阻塞 ,有事件发生 才会恢复向下运行

            log.info("遍历一下~~~");
         selector.select();

         //处理事件 ,selectionKeys 包含了所以可发生的事件 !
          //  Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            Thread.sleep(5000);

            while (iterator.hasNext()){
                log.info("遍历一下 SelectionKey ~~~");
                //SelectionKey :代表了每一个事件
                SelectionKey key = iterator.next();

               /*
                * 事件来了之后会加入 一个集合a里面 ,然后用 下面的if分支进行判断并且处理。
                * 处理完毕之后加入没有删除该事件  ,下次   while (iterator.hasNext()){}遍历的第一个会是他,
                * 例子:第一次 遍历到accept 事件 ,处理: 建立连接
                *      第二次 不删除a集合里的第一次的事件 还是会遍历到accept 事件,但是此时没有 accept事件报错
               */
                iterator.remove();

                //区分事件类型
                if (key.isAcceptable()){
                    log.debug("is accept things ,key:{}",key);
                    //4. 拿到 触发事件的channel  假设将下面的代码进行注释 ,则会发生
                    //log.info("遍历一下~~~");无线循环 ,未处理的事件会重新加入队列
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    //建立连接
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false) ;

                    //将 socketchannel注册给selector   SelectionKey 《--- (sscKey 管理ServerSocketChannel)
                    //                                             《 -- (sc 管理Channel)
                    SelectionKey scKey = sc.register(selector, 0, null);

                    //设置关注事件类型
                    scKey.interestOps(SelectionKey.OP_READ) ;
                    log.debug("channel:{}",sc);
                }else if (key.isReadable()){
                    log.debug("is read things ,key:{}",key);
                    SocketChannel channel = (SocketChannel) key.channel();
                    //读取事件
                    ByteBuffer buffer = ByteBuffer.allocate(166);
                    //读取数据
                    channel.read(buff) ;
                    buffer.flip() ;// 切换读模式
                }



                //key.cancel:将事件取消
            }
        }
    }

}

客户端正常断开,异常断开

在sc 监听读的时候 ,如果客户端异常断开 ,服务端会抛出错误 ,此时 catch 需要抓住错误,并且删除 事件 (防止事件重复消费) ,
当客户端是正常退出的时候 , 不会被抓捕 ,但是 channel.read(buff) 会返回 -1 ,此时 就需要

1.一个客户端正常退出 会无线遍历
在这里插入图片描述
2.一个客户端异常退出
在这里插入图片描述
解决方案:

  }else if (key.isReadable()){
                    // try{} :防止 客户端断开 ,channel断联会抛错 ,还需要删除key 事件
                   try{
                       log.debug("is read things ,key:{}",key);
                       SocketChannel channel = (SocketChannel) key.channel();
                       //读取事件
                       ByteBuffer buffer = ByteBuffer.allocate(166);
                       //读取数据
                       int read = channel.read(buff);
                       if (read == -1){
                           //客户端正常退出
                           key.cancel();
                       }

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

                   }catch (Exception e){
                      // key.cancel() ;
                       e.printStackTrace();
                   }
                }

在这里插入图片描述

在这里插入图片描述

消息的处理边界

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
每个socket 都需要自己独有的socket ()

写数据

@Slf4j
public class Server03 {

    public static void main(String[] args) throws IOException {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false) ;
        Selector selector = Selector.open();

        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        serverSocketChannel.bind(new InetSocketAddress(8080));
        while (true){
            selector.select() ;

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                if (selectionKey.isAcceptable()){
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false) ;

                    //写数据
                    StringBuilder stringBuilder = new StringBuilder();
                    for (int i=0;i< 300000000;i++){
                        stringBuilder.append("a") ;
                    }
                    //Charset.defaultcharset() :指的是jvm输入流、输出流默认使用的编码/解码方式。
                    ByteBuffer buffer = Charset.defaultCharset().encode(stringBuilder.toString());



                    //写一次看是否写完 ,没写完让 selectionKey关注一个可写事件
                    //将 剩余数据挂到 selectionKey 上
                    log.info("开始写数据咯~~");
                    int write = socketChannel.write(buffer);
                    System.out.println(write);

                    //返回值代表实际写的数据
                    // 如果有剩余数据
                    if (buffer.hasRemaining()){
                      selectionKey.interestOps(selectionKey.interestOps()+SelectionKey.OP_WRITE);
                    //把未写完的数据挂到sckey ,当socketchannel空了下次就会过来 触发写事件
                        selectionKey.attach(buffer) ;
                    }
                }else if (selectionKey.isWritable()){
                    ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    int write = channel.write(buffer);
                    System.out.println(write);

                    //6 清理工作
                    if (!buffer.hasRemaining()){
                        selectionKey.attach(null) ;
                        //数据写完了 不需要关注可写事件了
                        selectionKey.interestOps(selectionKey.interestOps()-SelectionKey.OP_WRITE) ;
                        if (!buffer.hasRemaining()){
                            //清除上面的 buffer
                            selectionKey.attach(null);
                        }
                    }

                }
            }
        }


    }
}


客户端读数据


@Slf4j
public class WriteClient {

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

        //与服务器建立连接
        socketChannel.connect(new InetSocketAddress("localhost",8080));
        ByteBuffer buffer = ByteBuffer.allocate(50000000);
        int count = 0 ;
        //接收数据
        while (true){
            log.info("开始打印~~~");
            count += socketChannel.read(buffer);
            System.out.println(count);
            buffer.clear() ;
        }
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值