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

上次说了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消息都将在队列中排队等等。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章