Java BIO

BIO

BIO 网络模型实际上就是使用传统的 Java IO 编程,相关联的类和接口都在 java.io 下。
BIO 模型到底是个什么玩意?
BIO (blocking I/O) 同步阻塞,看这个翻译,blocking I/O,实际上就能看出来,就是阻塞,当客户端发起请求的时候,服务端就会开启一个线程,专门为这个客户端提供对应的读写操作,只要客户端发起了,这个服务端的线程就一直保持存在,就算客户端啥也不干,那也在那里开着,就是玩。
来整一个服务端和客户端来看看是什么样子的模型。
Server端:

  1. public class TimeServer {
  2. public static void main(String[] args){
  3. ServerSocket server=null;
  4. try {
  5. server=new ServerSocket(18080);
  6. System.out.println("服务启动 端口:18080...");
  7. while (true){
  8. Socket client = server.accept();
  9. //每次接收到一个新的客户端连接,启动一个新的线程来处理
  10. new Thread(new TimeServerHandler(client)).start();
  11. }
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }finally {
  15. try {
  16. server.close();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }
  23. public class TimeServerHandler implements Runnable{
  24. private Socket clientProxxy;
  25. public TimeServerHandler(Socket clientProxxy) {
  26. this.clientProxxy = clientProxxy;
  27. }
  28. @Override
  29. public void run() {
  30. BufferedReader reader = null;
  31. PrintWriter writer = null;
  32. try {
  33. reader = new BufferedReader(new InputStreamReader(clientProxxy.getInputStream()));
  34. writer =new PrintWriter(clientProxxy.getOutputStream()) ;
  35. while (true) {//因为一个client可以发送多次请求,这里的每一次循环,相当于接收处理一次请求
  36. String request = reader.readLine();
  37. if (!"GET CURRENT TIME".equals(request)) {
  38. writer.println("BAD_REQUEST");
  39. } else {
  40. writer.println(Calendar.getInstance().getTime().toLocaleString());
  41. }
  42. writer.flush();
  43. }
  44. } catch (Exception e) {
  45. throw new RuntimeException(e);
  46. } finally {
  47. try {
  48. writer.close();
  49. reader.close();
  50. clientProxxy.close();
  51. } catch (IOException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }
  56. }

Client端:

  1. public class TimeClient {
  2. public static void main(String[] args) {
  3. BufferedReader reader = null;
  4. PrintWriter writer = null;
  5. Socket client=null;
  6. try {
  7. client=new Socket("127.0.0.1",18080);
  8. writer = new PrintWriter(client.getOutputStream());
  9. reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
  10. while (true){//每隔5秒发送一次请求
  11. writer.println("GET CURRENT TIME");
  12. writer.flush();
  13. String response = reader.readLine();
  14. System.out.println("Current Time:"+response);
  15. Thread.sleep(5000);
  16. }
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. } finally {
  20. try {
  21. writer.close();
  22. reader.close();
  23. client.close();
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }

执行结果如下:

服务端:

  1. 服务启动 端口:18080...

客户端:

  1. Current Time:2021-6-16 11:37:34
  2. Current Time:2021-6-16 11:37:39
  3. Current Time:2021-6-16 11:37:44
  4. Current Time:2021-6-16 11:37:49
  5. Current Time:2021-6-16 11:37:54
  6. Current Time:2021-6-16 11:37:59
  7. Current Time:2021-6-16 11:38:04

我们使用的是 Client 发送请求指令”GET CURRENT TIME”给server端,每隔5秒钟发送一次,每次 Server 端都返回当前时间。
这就相当于是多个 Client 同时请求 Server ,每个 Client 创建一个线程来进行处理.
传统BIO的局限性 - 图1
Accpetor thread 只负责与 Client 建立连接,worker thread用于处理每个thread真正要执行的操作。
在到了这里的时候,就发现了一些事情,感觉不对了有没有,

BIO 的局限性一

  • 每一个 Client 建立连接后都需要创建独立的线程与 Server 进行数据的读写,业务处理。

这时候就会出现什么样子的问题,如果说有上千个客户端的时候,服务端就会创建上千个线程,有多少客户端,就创建多少个线程,这对于 Java 来说, 代价实在是太大。
如果说线程在到达一定数量的时候,在做线程的切换的时候,大家可以想象一下对资源的浪费是什么样子的,一个线程和一百个线程甚至超过一千个线程的时候,在线程进行上下文切换的时候,会出现什么样子的问题。
针对某个线程来说,这种 BIO 的模型,在这个线程读取数据的时候,如果没有数据了,那线程就开始了阻塞,为了能够做到响应数据,这个线程在一直阻塞,这时候有新的请求的时候,线程阻塞,好吧,那只能等,一直处于一个等待不能执行的状态。

BIO 的局限性二

  • 并发数大的时候,会创建非常多的线程来处理连接,系统资源会出现非常大的开销。

    BIO 的局限性三

  • 在线程阻塞的时候,会造成资源的浪费。

实际上说是三个局限性,总得来说,他就是一个局限,浪费资源,开销大,这是非常致命的,一个小小的功能的话,占用的服务器大量的资源,只是硬件上这块的内容,都得增加多少的成本,现在万恶的资本家们,抱着能省就省的原则,又扯远了,还是回归到 BIO 上。
而且这种 BIO 的模型,在本质上说,实际上就相当于是一个 1:1 的关系,而这种 1:1 的关系如果在客户端非常多的时候,创建的线程数所浪费的资源是非常巨大的,所以就出现了另外一种模型,NIO 模型。而 NIO 模型实际上目前使用的那可真的是太普遍了,比如说 Netty ,都是选择使用这种模型,不再继续使用 BIO 的模型了。