采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。Blocking-IO是典型的一请求一应答的模型。
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数量呈1:1的正比关系。由于线程是虚拟机非常宝贵的系统资源,随着并发访问量的不断增长,需求的线程数也会不断上升,可能会导致线程堆栈溢出、创建新线程失败等问题,最终会引起服务器宕机或僵死。
对于这个模型的优化,有一个伪异步的实现,就是用线程池取代一个连接分配一条线程的作法,这样的话它占用的资源就变成可控的,无论有多少个客户端并发访问都不会导致资源枯竭而宕机。
下面将以Echo服务器为例,展示在Java中如何建立BIO通信模型的服务端与客户端。
package io.bio; 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.ExecutorService; import java.util.concurrent.Executors; public class BioEchoServer { public static void main(String[] args) { int port = 8080; if (args != null && args.length > 0) { try { port = Integer.valueOf(args[0]); } catch (Exception e) { } } ServerSocket server = null; try { server = new ServerSocket(port); System.out.println("Server start in:" + port); Socket socket = null; EchoTaskPool pool = new EchoTaskPool(10); while (true) { socket = server.accept(); pool.execute(new EchoHandler(socket)); // new Thread(new EchoHandler(socket)).start(); } } catch (IOException e) { e.printStackTrace(); } finally { if (server != null) { try { server.close(); System.out.println("Server shut down"); } catch (IOException e) { e.printStackTrace(); } server = null; } } } private static class EchoHandler implements Runnable { private Socket socket; public EchoHandler(Socket socket) { this.socket = socket; } @Override public void run() { BufferedReader in = null; PrintWriter out = null; try { in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); String body = null; while (true) { body = in.readLine(); if (body == null) { break; } System.out.println("Received:" + body); out.println(body); } } catch (IOException e) { e.printStackTrace(); if (in != null) { try { in.close(); } catch (IOException e1) { e1.printStackTrace(); } in = null; } if (out != null) { out.close(); out = null; } if (this.socket != null) { try { this.socket.close(); } catch (IOException e1) { e1.printStackTrace(); } this.socket = null; } } } } private static class EchoTaskPool { private ExecutorService executor; public EchoTaskPool(int maxPoolSize) { executor = Executors.newFixedThreadPool(maxPoolSize); } public void execute(Runnable task) { executor.execute(task); } } }
package io.bio; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class BioEchoClient { public static void main(String[] args) { int port = 8080; if (args != null && args.length > 0) { try { port = Integer.valueOf(args[0]); } catch (Exception e) { } } Socket socket = null; BufferedReader in = null; BufferedReader input = null; PrintWriter out = null; try { socket = new Socket("127.0.0.1", port); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); input = new BufferedReader(new InputStreamReader(System.in)); out = new PrintWriter(socket.getOutputStream(), true); String body = null; while (true) { body = input.readLine(); if (body == null || body.length() == 0) { break; } System.out.println("Send:" + body); out.println(body); System.out.println("Send over"); body = in.readLine(); System.out.println("Received:" + body); } } catch (Exception e) { e.printStackTrace(); if (in != null) { try { in.close(); } catch (IOException e1) { e1.printStackTrace(); } in = null; } if (input != null) { try { input.close(); } catch (IOException e1) { e1.printStackTrace(); } input = null; } if (out != null) { out.close(); out = null; } if (socket != null) { try { socket.close(); } catch (IOException e1) { e1.printStackTrace(); } socket = null; } } } }
由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。
当对Socket的输入流进行读取操作时,它会一直阻塞下去,直到发生如下三种事件:有数据可读、可用数据读取完毕、发生空指针或者I/O异常。这意味着当对方发送请求或者应答消息比较缓慢、或者网络传输较慢时,读取输入流的一方的通信线程将被长时间阻塞,在此期间,其他的消息只能在队列中一直排队。
当对Socket的输出流进行写操作时,它将会被阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。当消息的接收方处理缓慢的时候,将不能及时地从缓冲区读取数据,最终写入方不能再向缓冲区写入数据,此时将无期限阻塞直到再次可写或发生异常。