美文网首页
【网络编程】伪异步I/O编程

【网络编程】伪异步I/O编程

作者: 程就人生 | 来源:发表于2023-02-17 11:04 被阅读0次

    上次说了BIO编程。为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来人们对它的线程模型进行了优化,服务器端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M对应线程池N的比例关系。通过线程池可以灵活调配线程资源,设置线程的最大值,防止由于海量并发客户端接入导致线程耗尽。这就是伪异步I/O编程。

    如下图所示,采用线程池和任务队列可以实现伪异步I/O通信框架。

    图片

    伪异步I/O服务器端通信模型服务器端代码:

    package com.test.aio1;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    /**
     * 伪异步I/O服务器端
     * @author 程就人生
     * @Date
     */
    public class HelloServer {
    
      public static void main( String[] args ){
        int port = 8080;
            ServerSocket serverSocket = null;
            try {
          serverSocket = new ServerSocket(port);
          Socket socket = null;
          HelloServerHandleExecutePool execute = new HelloServerHandleExecutePool(50, 1000);
          // 通过无限循环监听客户端连接
          while(true){
            // 如果没有客户端连接,则阻塞在Accept操作上
            socket = serverSocket.accept();
            // 如果有客户端连接,则加入线程池执行
            execute.execute(new HelloHandler(socket));
          }
        } catch (IOException e) {
          e.printStackTrace();
        }
        }
    }
    
    /**
     * 线程池类
     * @author 程就人生
     * @Date
     */
    class HelloServerHandleExecutePool{  
    
      private ExecutorService executorService;  
      
      public HelloServerHandleExecutePool(int maxPoolSize, int queueSize) {    
        executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<java.lang.Runnable>(queueSize));
      }
      
      public void execute(java.lang.Runnable task){
        executorService.execute(task);
      }  
    }
    
    /**
     * hello处理类
     * @author 程就人生
     * @Date
     */
    class HelloHandler implements Runnable{
      
      private Socket socket;
    
      public HelloHandler(Socket socket) {
        this.socket = socket;
      }
    
      public void run() {
        BufferedReader reader = null;
        PrintWriter writer = null;
        try {
          reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
          // 返回给客户端,是否刷新设置为true
          writer = new PrintWriter(this.socket.getOutputStream(), true);
          String body = null;
          while(true){
            body = reader.readLine();
            if(body == null){
              break;
            }
            System.out.println("服务器端接收到的:" + body);
            writer.println("来自服务器端的响应!");        
          }
        } catch (IOException e) {
          e.printStackTrace();
          // 出现异常时,对资源的释放
          if(reader != null){
            try {
              reader.close();
            } catch (IOException e1) {
              e1.printStackTrace();
            }
          }
          if(writer != null){
            writer.close();
            writer = null;
          }
          if(socket != null){
            try {
              socket.close();
            } catch (IOException e1) {
              e1.printStackTrace();
            }
          }
        }
      }  
    }
    
    伪异步I/O服务器端的主函数有了变化,先创建一个Hello服务处理类的线程池。当接收到新的客户端连接时,将请求soket封装成一个Task,然后调用线程池的execute方法执行,从而避免每个客户端连接时都创建一个新线程。由于线程池和消息队列都是有界的。因此,无论客户端并发连接数多大,都不会导致服务器端线程个数过于膨胀而导致内存溢出,相比于传统的BIO线程模型是一种改良。 运行服务器端结果。 图片 运行客户端结果 虽然伪异步I/O通信框架采用了线程池实现,因此避免了每个请求都创建一个独立线程造成的线程资源耗尽问题,但它的底层的通信依旧采用同步阻塞模型,因此无法从根本上解决问题。一起看下socket的InputStream方法。 图片 图片

    再看下InputStream类。


    图片

    当对Socket的输入流进行读取操作时,它会一直堵塞下去,直到发生三种事件:

    • 有数据可读。
    • 可用数据已经读完。
    • 发生异常。

    如果对方发生请求或者应答比较缓慢,或者网络传输比较慢,读取输入流一方的通信线程将会被长时间阻塞。如果对方要60s才能将数据发送完成,读取一方的I/O也会被阻塞60s。在这60s期间,其他接入的客户端只能在消息队列中排队。伪异步I/O只是对BIO线程模型的一个简单优化,无法从根本上解决同步IO导致的通信线程阻塞问题。
    伪异步I/O的缺点显而易见。

    • 如果客户端请求量增加,会导致服务器端处理应答消息缓慢。
    • 如果有故障节点,由于读取输入流的阻塞的,它会被阻塞,直到释放。
    • 如果所有的线程都被故障服务器阻塞,那么后续的IO消息都将在队列中排队等等。

    相关文章

      网友评论

          本文标题:【网络编程】伪异步I/O编程

          本文链接:https://www.haomeiwen.com/subject/oubmkdtx.html