• [编织消息框架][网络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

  • 相关阅读:
    美化的滚动条
    网站系统开发参考网址
    正则表达式获取URL参数
    类实例 及 实例化对象 对象引用
    C# 静态方法 静态属性 调用静态方法
    C# 静态方法调用非静态方法
    winform 窗体间传值
    从数据库中读出数据并输出
    数据库链接字符串
    DbHelper
  • 原文地址:https://www.cnblogs.com/solq111/p/6732365.html
Copyright © 2020-2023  润新知