【愚公系列】2022年01月 Java教学课程 69-NIO结合Scoket的网络通信

举报
愚公搬代码 发表于 2022/01/15 23:19:57 2022/01/15
【摘要】 一.NIO结合Scoket的网络通信 1.NIO通道客户端客户端实现步骤打开通道指定IP和端口号写出数据释放资源示例代码public class NIOClient { public static void main(String[] args) throws IOException { //1.打开通道 SocketChannel socketChann...

一.NIO结合Scoket的网络通信

1.NIO通道客户端

  • 客户端实现步骤

    1. 打开通道
    2. 指定IP和端口号
    3. 写出数据
    4. 释放资源
  • 示例代码

    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("NIO结合Scoket的网络通信".getBytes());
            socketChannel.write(byteBuffer);
    
            //4.释放资源
            socketChannel.close();
        }
    }
    

2. NIO通道服务端

  • NIO通道

    • 服务端通道

      只负责建立建立,不负责传递数据

    • 客户端通道

      建立建立并将数据传递给服务端

    • 缓冲区

      客户端发送的数据都在缓冲区中

    • 服务端通道内部创建出来的客户端通道

      相当于客户端通道的延伸用来传递数据

  • 服务端实现步骤

    1. 打开一个服务端通道
    2. 绑定对应的端口号
    3. 通道默认是阻塞的,需要设置为非阻塞
    4. 此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
    5. 如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
    6. 获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
    7. 给客户端回写数据
    8. 释放资源
  • 示例代码

    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();
                }
            }
        }
    }
    

3. NIO通道练习

  • 客户端

    • 实现步骤

      1. 打开通道
      2. 指定IP和端口号
      3. 写出数据
      4. 读取服务器写回的数据
      5. 释放资源
    • 示例代码

      public class Clinet {
          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 byteBuffer1 = ByteBuffer.wrap("NIO结合Scoket的网络通信双向通信版".getBytes());
              socketChannel.write(byteBuffer1);
        		// 手动写入结束标记
              socketChannel.shutdownOutput();
      
              System.out.println("数据已经写给服务器");
              // 4.读取服务器写回的数据
              ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
              int len;
              while((len = socketChannel.read(byteBuffer2)) != -1){
                  byteBuffer2.flip();
                  System.out.println(new String(byteBuffer2.array(),0,len));
                  byteBuffer2.clear();
              }
              // 5.释放资源
              socketChannel.close();
          }
      }
      
  • 服务端

    • 实现步骤

      1. 打开一个服务端通道
      2. 绑定对应的端口号
      3. 通道默认是阻塞的,需要设置为非阻塞
      4. 此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
      5. 如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
      6. 获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
      7. 给客户端回写数据
      8. 释放资源
    • 示例代码

      public class Sever {
          public static void main(String[] args) throws IOException {
              // 1,打开一个服务端通道
              ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
              // 2,绑定对应的端口号
              serverSocketChannel.bind(new InetSocketAddress(10000));
              // 3,通道默认是阻塞的,需要设置为非阻塞
              serverSocketChannel.configureBlocking(false);
              // 4,此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
              while(true){
                  //  5,如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
                  SocketChannel socketChannel = serverSocketChannel.accept();
                  if(socketChannel != null){
                      System.out.println("此时有客户端来连接了");
                      // 6,获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
                      ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
                      //socketChannel.read(byteBuffer1);
                      int len;
                      //针对于缓冲区来讲
                          //如果 从添加数据 ----> 获取数据 flip
                          //如果 从获取数据 ----> 添加数据 clear
                      while((len = socketChannel.read(byteBuffer1)) != -1){
                          byteBuffer1.flip();
                          System.out.println(new String(byteBuffer1.array(),0,len));
                          byteBuffer1.clear();
                      }
      
                      System.out.println("接收数据完毕,准备开始往客户端回写数据");
                      // 7,给客户端回写数据
                      ByteBuffer byteBuffer2 = ByteBuffer.wrap("您来了!!".getBytes());
                      socketChannel.write(byteBuffer2);
                      // 8,释放资源
                      socketChannel.close();
                  }
              }
          }
      }
      

4. NIO通道练习优化

  • 存在问题

    服务端内部获取的客户端通道在读取时,如果读取不到结束标记就会一直阻塞

  • 解决方案

    将服务端内部获取的客户端通道设置为非阻塞的

  • 示例代码

    // 客户端
    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("NIO结合Scoket的网络通信".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();
        }
    }
    // 服务端
    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();
                }
            }
        }
    }
    

5. NIO选择器

  • 概述

    选择器可以监视通道的状态,多路复用
    在这里插入图片描述
    在这里插入图片描述

  • 选择器对象

    • Selector

      选择器对象

    • SelectionKey

      绑定的key

    • SelectableChannel

      能使用选择器的通道

      • SocketChannel
      • ServerSocketChannel

6. NIO选择器改写服务端

  • 实现步骤

    1. 打开一个服务端通道(open)

    2. 绑定对应的端口号

    3. 通道默认是阻塞的,需要设置为非阻塞

    4. 打开一个选择器(门卫大爷)

    5. 将选择器绑定服务端通道,并监视服务端是否准备好

    6. 如果有客户端来连接了,大爷会遍历所有的服务端通道,谁准备好了,就让谁来连接
      连接后,在服务端通道内部,再创建一个客户端延伸通道

    7. 如果客户端把数据传递过来了,大爷会遍历所有的延伸通道,谁准备好了,谁去接收数据

在这里插入图片描述

  • 代码实现

    // 客户端
    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("NIO结合Scoket的网络通信".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();
        }
    }
    // 服务端
    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();
                    }
                }
            }
        }
    }
    
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。