[编织消息框架][网络IO模型]BIO

既然跟网络内容有关就不得不学习网络IO模型,时代在进步,技术也在进步,采取使用那种网络IO模型就已经确定应用程序规模

阻塞IO(blocking IO)

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

图1 阻塞IO

   

大部分的IO接口都是阻塞型的。所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。

  当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来

所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。

  对于网络编程,在第一阶段系统内核阻塞等侍接收完整数据包,主进程/线程无法执行运算同响应其它请求,非常浪费硬件资源,解决方案是每个socket开个线程/进程独立处理

 1 public final class ServerBio {
 2     private static int DEFAULT_PORT = 12345;
 3     private static ServerSocket server;
 4     private static AtomicInteger ai = new AtomicInteger();
 5 
 6     public static void main(String[] args) throws Exception {
 7         try {
 8             server = new ServerSocket(DEFAULT_PORT);
 9             System.out.println("服务器已启动,端口号:" + DEFAULT_PORT);
10             while (true) {
11                 Socket socket = server.accept();
12                 ai.incrementAndGet();
13                 new Thread(new ServerHandler(socket)).start();
14             }
15         } finally {
16             if (server != null) {
17                 System.out.println("服务器已关闭。");
18                 server.close();
19                 server = null;
20             }
21         }
22     }
23 
24     public static class ServerHandler implements Runnable {
25     private Socket socket;
26     private BufferedReader in = null;
27     private PrintWriter out = null;
28 
29     public ServerHandler(Socket socket) {
30         this.socket = socket;
31     }
32 
33     @Override
34     public void run() {
35         try {
36         in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
37         out = new PrintWriter(socket.getOutputStream(), true);
38         String body;
39         while (true) {
40             if ((body = in.readLine()) == null) {
41                 continue;
42             }
43             System.out.println("服务器收到消息:" + body);
44             out.println(ai.get());
45         }
46         } catch (Exception e) {
47          
48         } finally {
49             if (in != null) {
50                 try {
51                     in.close();
52                 } catch (IOException e) {
53                     e.printStackTrace();
54                 }
55             }
56             if (out != null) {
57                 out.close();
58             }
59             if (socket != null) {
60                 try {
61                     socket.close();
62                 } catch (IOException e) {
63                     e.printStackTrace();
64                 }
65             }
66         }
67     }
68 }
 1 public class ClientBio {
 2     private static int DEFAULT_SERVER_PORT = 12345;
 3     private static String DEFAULT_SERVER_IP = "127.0.0.1";
 4     private static AtomicInteger ai = new AtomicInteger();
 5 
 6     private Socket socket = null;
 7     private BufferedReader in = null;
 8     private PrintWriter out = null;
 9 
10     public static void main(String[] args) throws InterruptedException {
11         while (true) {
12             send("xxxxxx");
13             Thread.sleep(1);
14         }
15     }
16 
17     public static void send(String body) {
18         new ClientBio().send(DEFAULT_SERVER_PORT, body);
19     }
20 
21     public void send(int port, String body) {
22         try {
23             socket = new Socket(DEFAULT_SERVER_IP, port);
24             in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
25             out = new PrintWriter(socket.getOutputStream(), true);
26             out.println(body);
27             ai.incrementAndGet();
28             System.out.println("客户端 接收:" + in.readLine());
29         } catch (Exception e) {
30             e.printStackTrace();
31         }
32     }
33 }

BIO有个致命的缺点,由于线程/进程资源是有限的,在测试发现当开了500线程左右每接收/创建一个socket时间变得越来越长,也就是说采用BIO模型的瓶颈在500左右(大众机器)

解决方案也简单:线程是有限的,那么就用池程线来重用线程

ServerBioPool.class 只需要添加ExecutorService pool 替换掉Thread即可

 1     private static ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*10);  
 2     public static void main(String[] args) throws Exception {
 3     try {
 4         server = new ServerSocket(DEFAULT_PORT);
 5         System.out.println("服务器已启动,端口号:" + DEFAULT_PORT);
 6         while (true) {
 7             Socket socket = server.accept();
 8             ai.incrementAndGet();
 9             pool.submit(new ServerHandler(socket));
10          }
11     } finally {
12         // 一些必要的清理工作
13         if (server != null) {
14             System.out.println("服务器已关闭。");
15             server.close();
16             server = null;
17             }
18         }
19     }

上面应用场景比较有限,如果是长连接不释放socket资源的话,每个socket占用一个thread,用thread pool只能优化thread创建和销毁的频率并不能解决thread不足问题,读者可以试下把线程数改成800再测试。

现实与理想差距还是很大的

我们来论证瓶颈是否出现在线程上,屏蔽掉 ServerBioPool.class ClientBio.class 发送接收处理

    public static void main(String[] args) throws Exception {
        server = new ServerSocket(DEFAULT_PORT);
        System.out.println("服务器已启动,端口号:" + DEFAULT_PORT);
        while (true) {
            Socket socket = server.accept();
            ai.incrementAndGet();
            System.out.println(ai.get());
        }
    }

ClientBio.class

    public void send(int port, String body) {
        try {
            socket = new Socket(DEFAULT_SERVER_IP, port);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

结果打印出的数字能突破成千上万

如果client使用bio是无影响的,因为由始至终只有一个socket

小结:使用BIO模型,性能屏颈受线程/进程数上限影响,client可以使用bio

原文地址:https://www.cnblogs.com/solq111/p/6732365.html