第十一章_网络编程(2)_NIO服务端

一、NIO

1 - NIO概述

  • 什么是NIO:No Blocking IO,非阻塞型IO
    • Java NIO全称java non-blocking IO, 是指JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(即New IO),是同步非阻塞的
  • 什么是BIO:Blocking IO,阻塞型IO
    • 当客户端有连接请求时,服务端就会需要启动一个线程对客户端的连接进行数据读写,如果客户端不进行读写了,那么这个线程也会等着,这样就会造成阻塞
  • 阻塞IO的弊端:在等待的过程中,什么事也做不了
  • 非阻塞IO的好处:不需要一直等待,当一切就绪了再去做
  • NIO与BIO的区别
    • BIO是阻塞的,NIO是非阻塞的
    • BIO是面向流的,NIO是面向缓冲区的
    • BIO中数据传输是单向的,NIO中的缓冲区是双向的

2 - NIO三大模块

在这里插入图片描述

二、NIO缓冲区

1 - NIO缓冲区创建

方法名说明
static ByteBuffer allocate(长度)创建byte类型的缓冲区
static ByteBuffer wrap(byte[] array)创建一个有内容的byte类型缓冲区
    private static void method1() {
        ByteBuffer byteBuffer1 = ByteBuffer.allocate(5);
        /**
         * Relative <i>get</i> method.  Reads the byte at this buffer's
         * current position, and then increments the position.
         *
         * @return  The byte at the buffer's current position
         *
         * @throws BufferUnderflowException
         *          If the buffer's current position is not smaller than its limit
         */
        //public abstract byte get();
        //读取当前索引的字节,并增加索引
        for (int i = 0; i < 5; i++) {
            System.out.println(byteBuffer1.get());
        }
//        System.out.println(byteBuffer1.get());
    }
}

    private static void method2() {
        byte [] bytes = {97,98,99};
        ByteBuffer byteBuffer2 = ByteBuffer.wrap(bytes);
        //缓冲区的长度3
        //缓冲区里面的内容就是字节数组的内容.
        for (int i = 0; i < 3; i++) {
            System.out.println(byteBuffer2.get());
        }
//        System.out.println(byteBuffer2.get());

        ByteBuffer wrap = ByteBuffer.wrap("aaa".getBytes());
        for (int i = 0; i < 3; i++) {
            System.out.println(wrap.get());
        }
    }

2 - NIO缓冲区添加数据

  • 方法介绍
    在这里插入图片描述
public class Test {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        System.out.println(byteBuffer.position());//0
        System.out.println(byteBuffer.limit());//10
        System.out.println(byteBuffer.capacity());//10
        System.out.println("-------------------------------");
        byteBuffer.put((byte) 97);
        System.out.println(byteBuffer.position());//1
        System.out.println(byteBuffer.limit());//10
        System.out.println(byteBuffer.capacity());//10
        System.out.println("-------------------------------");
        byteBuffer.put("aaa".getBytes());
        System.out.println(byteBuffer.position());//4
        System.out.println(byteBuffer.limit());//10
        System.out.println(byteBuffer.capacity());//10
        System.out.println("-------------------------------");
        byteBuffer.position(1);
        byteBuffer.limit(5);
        System.out.println(byteBuffer.position());//1
        System.out.println(byteBuffer.limit());//5
        System.out.println(byteBuffer.capacity());//10
        System.out.println("-------------------------------");
        byteBuffer.put("23".getBytes());
        System.out.println(byteBuffer.remaining());//2
        System.out.println(byteBuffer.hasRemaining());//true
    }
}

3 - NIO缓冲区获取数据

方法名介绍
flip()切换读写模式(写à读)
get()读一个字节
get(byte[] dst)读多个字节
get(int index)读指定索引的字节
rewind()将position设置为0,可以重复读
clear()数据读写完毕(读->写)
array()将缓冲区转换成字节数组返回
  • flip的理解:Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式
    • 也就是说调用flip()之后,读/写指针position指到缓冲区头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小)

在这里插入图片描述

    private static void method1() {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put("abc".getBytes());

//        flip()  切换读写模式(写读)
        byteBuffer.flip();
//        get()   读一个字节
        while(byteBuffer.limit() != byteBuffer.position()){
            System.out.println((char) byteBuffer.get());
        }
    }

    private static void method2() {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put("abc".getBytes());
        byteBuffer.flip();
        //        get(byte[] dst) 读多个字节
        byte [] bytes = new byte[byteBuffer.limit()];
        byteBuffer.get(bytes);
        System.out.println(new String(bytes));
    }

    private static void method3() {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put("abc".getBytes());
        byteBuffer.flip();
//        get(int index)  读指定索引的字节
        System.out.println((char) byteBuffer.get(0));

//        rewind()    将position设置为0,可以重复读
        byteBuffer.rewind();
        for (int i = 0; i < byteBuffer.limit(); i++) {
            System.out.println((char) byteBuffer.get());
        }
//        clear()     数据读写完毕(读->写)
        byteBuffer.clear();
        byteBuffer.put("qqq".getBytes());
//        array()     将缓冲区转换成字节数组返回

        byte[] bytes = byteBuffer.array();
        System.out.println(new String(bytes));
    }
  • flip和clear总结
    • 获取缓冲区里面数据之前,需要调用flip方法
    • 再次写数据之前,需要调用clear方法(但是数据还未消失,等再次写入数据,被覆盖了才会消失)

三、NIO通道

1 - NIO通道简单实现

  • NIO通道
    • 服务端通道:只负责建立,不负责传递数据
    • 客户端通道:建立并将数据传递给服务端
    • 缓冲区:客户端发送的数据都在缓冲区中
    • 服务端通道内部创建出来的客户端通道:相当于客户端通道的延伸用来传递数据
  • NIO通道客户端简单实现
    • ①.打开通道
    • ②.指定ip和端口号
    • ③.写出数据
    • ④.释放资源
  public class NIOClient {
      public static void main(String[] args) throws IOException {
          //1.打开通道
          SocketChannel socketChannel = SocketChannel.open();
          //2.指定IP和端口号
          socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
          //3.写出数据
          ByteBuffer byteBuffer = ByteBuffer.wrap("一点寒毛先制".getBytes());
          socketChannel.write(byteBuffer);
          //4.释放资源
          socketChannel.close();
      }
  }
  • NIO通道服务端简单实现
    • ①.打开一个服务端通道
    • ②.绑定对应的端口号
    • ③.通道默认是阻塞的,需要设置为非阻塞
    • ④.此时没有门卫大爷,所以需要经常看一下有没有连接发过来没
    • ⑤.如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
    • ⑥.获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
    • ⑦.给客户端回写数据
    • ⑧.释放资源
  public class NIOServer {
      public static void main(String[] args) throws IOException {
  //        1.打开一个服务端通道
          ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  //        2.绑定对应的端口号
          serverSocketChannel.bind(new InetSocketAddress(10000));
  //        3.通道默认是阻塞的,需要设置为非阻塞
              //如果传递true 表示通道设置为阻塞通道...默认值
              //如果传递false 表示通道设置为非阻塞通道
          serverSocketChannel.configureBlocking(false);
  //        4.此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
          while (true) {
  //        5.如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
              //此时已经设置了通道为非阻塞
              //所以在调用方法的时候,如果有客户端来连接,那么会创建一个SocketChannel对象.
              //如果在调用方法的时候,没有客户端来连接,那么他会返回一个null
              SocketChannel socketChannel = serverSocketChannel.accept();
              //System.out.println(socketChannel);
              if(socketChannel != null){
  //        6.客户端将缓冲区通过通道传递给服务端,就到了这个延伸通道socketChannel里面
  //        7.服务端创建一个空的缓冲区装数据并输出
                  ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                  //获取传递过来的数据,并把他们放到byteBuffer缓冲区中.
                  //返回值:
                      //正数: 表示本次读到的有效字节个数.
                      //0   : 表示本次没有读到有效字节.
                      //-1  : 表示读到了末尾
                  int len = socketChannel.read(byteBuffer);
                  System.out.println(new String(byteBuffer.array(),0,len));
                //8.释放资源
                  socketChannel.close();
              }
          }
      }
  }

2 - NIO通道-服务器回写数据

  • client实现
  public class Clinet {
      public static void main(String[] args) throws IOException {
          SocketChannel socketChannel = SocketChannel.open();
          socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
          ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孙一棒棒".getBytes());
          socketChannel.write(byteBuffer1);
          System.out.println("数据已经写给服务器");
          ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
          int len;
          while((len = socketChannel.read(byteBuffer2)) != -1){
              System.out.println("客户端接收回写数据");
              byteBuffer2.flip();
              System.out.println(new String(byteBuffer2.array(),0,len));
              byteBuffer2.clear();
          }
          socketChannel.close();
      }
  }
  • server实现
  public class Sever {
      public static void main(String[] args) throws IOException {
          ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
          serverSocketChannel.bind(new InetSocketAddress(10000));
          serverSocketChannel.configureBlocking(false);
          while(true){
              SocketChannel socketChannel = serverSocketChannel.accept();
              if(socketChannel != null){
                  System.out.println("此时有客户端来连接了");
                	// 将服务端内部获取的客户端通道设置为非阻塞的
                  socketChannel.configureBlocking(false);
                  //获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
                  ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
                  //socketChannel.read(byteBuffer1);
                  int len;
                  //针对于缓冲区来讲
                      //如果 从添加数据 ----> 获取数据 flip
                      //如果 从获取数据 ----> 添加数据 clear
                  while((len = socketChannel.read(byteBuffer1)) > 0){
                      System.out.println("服务端接收发送数据");
                      byteBuffer1.flip();
                      System.out.println(new String(byteBuffer1.array(),0,len));
                      byteBuffer1.clear();
                  }
                  System.out.println("接收数据完毕,准备开始往客户端回写数据");
                  ByteBuffer byteBuffer2 = ByteBuffer.wrap("哎哟,真疼啊!!!".getBytes());
                  socketChannel.write(byteBuffer2);
                  socketChannel.close();
              }
          }
      }
  }

四、NIO选择器

  • NIO选择器概述:从底层看,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。单个线程通过Selector可以管理多个SelectableChannel,实际应用中管理多个请求连接。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源,比如内存,因此使用的线程越少越好。
    • 选择器可以监视通道的状态,多路复用
  • 选择器对象
    • Selector:选择器对象
    • SelectionKey:绑定的key
    • SelectableChannel:能使用选择器的通道
      • SocketChannel
      • ServerSocketChannel
  • NIO选择器改写服务端
//服务端
   public class Server {
       public static void main(String[] args) throws IOException {
           //1.打开服务端通道
           ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
           //2.让这个通道绑定一个端口
           serverSocketChannel.bind(new InetSocketAddress(10000));
           //3.设置通道为非阻塞
           serverSocketChannel.configureBlocking(false);
           //4.打开一个选择器
           //Selector --- 选择器
   //        SelectionKey --- 绑定通道后返回那个令牌
     //      SelectableChannel --- 可以使用选择器的通道
           Selector selector = Selector.open();
           //5.绑定选择器和服务端通道
           serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

           while(true){
               System.out.println("11");
               //选择器会监视客户端通道的状态.
               //6.返回值就表示此时有多少个客户端来连接.
               int count = selector.select();
               System.out.println("222");
               if(count != 0){
                   System.out.println("有客户端来连接了");
                   //7.会遍历所有的服务端通道.看谁准备好了,谁准备好了,就让谁去连接.
                   //获取所有服务端通道的令牌,并将它们都放到一个集合中,将集合返回.
                   Set<SelectionKey> selectionKeys = selector.selectedKeys();
                   Iterator<SelectionKey> iterator = selectionKeys.iterator();
                   while(iterator.hasNext()){
                       //selectionKey 依次表示每一个服务端通道的令牌
                       SelectionKey selectionKey = iterator.next();
                       if(selectionKey.isAcceptable()){
                           //可以通过令牌来获取到了一个已经就绪的服务端通道
                           ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                           //客户端的延伸通道
                           SocketChannel socketChannel = ssc.accept();
                           //将客户端延伸通道设置为非阻塞的
                           socketChannel.configureBlocking(false);
                           socketChannel.register(selector,SelectionKey.OP_READ);
                           //当客户端来连接的时候,所有的步骤已经全部执行完毕.
                       }else if(selectionKey.isReadable()){
                           //当前通道已经做好了读取的准备(延伸通道)
                           SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                           ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
                           //socketChannel.read(byteBuffer1);
                           int len;
                           while((len = socketChannel.read(byteBuffer1)) > 0){
                               byteBuffer1.flip();
                               System.out.println(new String(byteBuffer1.array(),0,len));
                               byteBuffer1.clear();
                           }
                           //给客户端的回写数据
                           socketChannel.write(ByteBuffer.wrap("哎哟喂好疼啊!!!".getBytes()));
                           socketChannel.close();
                       }
                       iterator.remove();
                   }
               }
           }
       }
   }

//客户端
   public class Clinet {
       public static void main(String[] args) throws IOException {
           SocketChannel socketChannel = SocketChannel.open();
           socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
           ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孙一棒棒".getBytes());
           socketChannel.write(byteBuffer1);
           System.out.println("数据已经写给服务器");
           ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
           int len;
           while((len = socketChannel.read(byteBuffer2)) != -1){
               System.out.println("客户端接收回写数据");
               byteBuffer2.flip();
               System.out.println(new String(byteBuffer2.array(),0,len));
               byteBuffer2.clear();
           }
           socketChannel.close();
       }
   }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休止符

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值