欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

BIO与NIO

程序员文章站 2022-04-24 10:29:12
...


解决Junit中无法Scanner的问题

一.BIO 阻塞式IO

使用java.io中的类
模拟一个服务器端(BIO),两个客户端

public class BIODemo {

    @Test//客户端1
    public void client1(){
        //客户端与服务器端通信的socket
        Socket socket = null;
        OutputStream os = null;
        try {
            //去连接ip(本机)+端口(8007)的服务器
            socket = new Socket("127.0.0.1",8007);
            os = socket.getOutputStream();
            Scanner scanner = new Scanner(System.in);
            String msg = scanner.next();
            os.write(msg.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(os!=null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Test//客户端2
    public void client2(){
        //客户端与服务器端通信的socket
        Socket socket = null;
        OutputStream os = null;
        try {
            //去连接ip(本机)+端口(8007)的服务器
            socket = new Socket("127.0.0.1",8007);
            os = socket.getOutputStream();
            Scanner scanner = new Scanner(System.in);
            String msg = scanner.next();
            os.write(msg.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(os!=null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    int len;
    char[] c = new char[1024];
    @Test // 服务端
    public void server(){
        //serverSocket对象,用来监听,如果监听到客户端的连接,则创建一个socket
        ServerSocket serverSocket = null;
        //接收到客户端连接之后,创建的服务器端与客户端通信的socket
        Socket socket = null;
        InputStream is = null;
       InputStreamReader reader = null;
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8007));
            //无线循环,一直处理客户端的连接
            while(true) {
                //阻塞,在接收到连接之前是阻塞的,一旦接收连接(客户端的socket)后,解阻塞,执行accept方法
                System.out.println("服务器等待连接");
                socket = serverSocket.accept();
                System.out.println("成功连接");
                System.out.println("等待数据");
                is = socket.getInputStream();
                reader = new InputStreamReader(is,"utf-8");
                //阻塞,如果没有收到数据会一直阻塞,read表示读了多少字节
                while((reader.read(c))!=-1) {
                    String content = new String(c);
                    System.out.println(content);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(reader!=null){
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

这种服务端无法处理并发访问的情况
1.当开启服务端时,服务端阻塞在accept()方法前等待连接
2.当开启客户端1时,客户端1阻塞(等待用户输入),服务器根据监听socket检测到连接,并调用accept()返回一个socket响应客户端1socket,此时服务器端阻塞在read()方法等待客户端1发送数据
3.若此时开启客户端2,服务器已被阻塞在read()方法处,服务器无法监听客户端2的连接,就无法处理客户端的请求了


解决方案:修改服务端代码,启动线程处理read()

  int len;
    char[] c = new char[1024];
    //serverSocket对象,用来监听,如果监听到客户端的连接,则创建一个socket
    ServerSocket serverSocket = null;
    //接收到客户端连接之后,创建的服务器端与客户端通信的socket
    Socket socket = null;
    InputStream is = null;
    InputStreamReader reader = null;
    @Test // 服务端
    public void server(){
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8007));
            //无线循环,一直处理客户端的连接
            while(true) {
                //阻塞,在接收到连接之前是阻塞的,一旦接收连接(客户端的socket)后,解阻塞,执行accept方法
                System.out.println("服务器等待连接");
                socket = serverSocket.accept();
                System.out.println("成功连接");
                System.out.println("等待数据");
                is = socket.getInputStream();
                //开启线程接收数据
                Thread thread  = new Thread(()->{
                    try {
                        reader = new InputStreamReader(is, "utf-8");
                        //阻塞,如果没有收到数据会一直阻塞,read表示读了多少字节
                        while ((reader.read(c)) != -1) {
                            String content = new String(c);
                            System.out.println(content);
                        }
                    }catch (Exception e){

                    }
                });
             thread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(reader!=null){
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
   }

弊端:服务器端,如果不活跃的线程比较多(无用的线程),开启多线程是很浪费的一种情况,而且在不考虑多线程的情况下,BIO是无法处理并发的(阻塞),所以需要一个单线程就能解决的技术(NIO)


二.NIO 非阻塞式IO

模拟一个服务器端(NIO),两个客户端
NIO使用java.nio的类实现非阻塞(configureBlocking(false))

public class NIODemo {

    @Test//客户端1
    public void client1(){
        //客户端与服务器端通信的socket
        Socket socket = null;
        OutputStream os = null;
        try {
            //去连接ip(本机)+端口(8007)的服务器
            socket = new Socket("127.0.0.1",8007);
            os = socket.getOutputStream();
            Scanner scanner = new Scanner(System.in);
            String msg = scanner.next();
            os.write(msg.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(os!=null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Test//客户端2
    public void client2(){
        //客户端与服务器端通信的socket
        Socket socket = null;
        OutputStream os = null;
        try {
            //去连接ip(本机)+端口(8007)的服务器
            socket = new Socket("127.0.0.1",8007);
            os = socket.getOutputStream();
            Scanner scanner = new Scanner(System.in);
            String msg = scanner.next();
            os.write(msg.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(os!=null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

       //设置一个集合保存客户端的socket,方便管理与记录
       List<SocketChannel> channels = new ArrayList<>();
       //字节缓冲区
       ByteBuffer byteBuffer = ByteBuffer.allocate(512);
      @Test
      public void server(){
          //ServerSocketChannel默认为阻塞的,但是与BIO不同,它可以设置为非阻塞
          ServerSocketChannel serverSocketChannel = null;
          //SocketChannel默认为阻塞的,但是与BIO不同,它可以设置为非阻塞
          SocketChannel socketChannel = null;
          try {
              serverSocketChannel = ServerSocketChannel.open();
              serverSocketChannel.bind(new InetSocketAddress(8007));
              //serverSocketChannel设置为非阻塞,使得在accept()处不会阻塞
              serverSocketChannel.configureBlocking(false);
              while(true){
                  //循环遍历客户端Socket
                  for(SocketChannel channel:channels){
                      int read =  channel.read(byteBuffer);
                      //如果读到数据,就打印数据
                      if(read>0) {
                          byteBuffer.flip();
                          byte[] b = byteBuffer.array();
                          System.out.println(new String(b));
                      }
                  }
                  socketChannel = serverSocketChannel.accept();
                  //如果没有收到客户端连接
                  if(socketChannel==null){
                  Thread.sleep(1500);
                  System.out.println("no connection");
                  }else{
                  //收到客户端连接
                  System.out.println("get connection");
                  //SocketChannel设置为非阻塞
                  socketChannel.configureBlocking(false);
                  //将客户端连接保存到集合中
                  channels.add(socketChannel);
                  }
              }
          } catch (Exception e) {
              e.printStackTrace();
          }finally{
           if(serverSocketChannel!=null){
               try {
                   serverSocketChannel.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
          }
      }
}

1.开启服务端,服务器端监听连接,先遍历客户端Socket的集合,如果接收到某一Socket传来的数据,就打印出来,没有数据,就继续,如果未收到连接,accept()不会阻塞,依然返回一个SocketChannel对象(null),并打印no connetion
2.开启一个客户端1,服务器端生成一个SocketChannel对象,将此对象放入集合中,并设置未非阻塞,这样代码执行到read()就不会阻塞
3.开启一个客户端2,与客户端1一致
4.客户端1发来数据,客户端遍历客户端Socket的集合,发现客户端1的socket有数据,则将这个数据打印出来

这种情况依然性能低下
假如集合中存了1000个客户端的Socket,只有200个发了数据,且这200也不是不停发数据,那么就有800一直没有发送数据,可是服务端还是要一直轮询1000个socket,去判断有没有数据发过来


解决方案:不让应用程序轮询集合,而是主动感知数据(操作系统层面而非编程语言),如果是Windows操作系统就调用select对Socket进行轮询,还是性能低下,如果是Linux操作系统就调用epoll函数,当有数据来了,可以通知是哪个Socket的数据,性能高