zoukankan      html  css  js  c++  java
  • Java IO学习笔记五:BIO到NIO

    作者:Grey

    原文地址: Java IO学习笔记五:BIO到NIO

    准备环境

    准备一个CentOS7的Linux实例:
    实例的IP:
    192.168.205.138

    我们这次实验的目的就是直观感受一下Socket编程中BIO模型和NIO模型的性能差异

    BIO

    准备服务端代码:

    import java.io.*;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * BIO Socket Server
     */
    public class SocketServerBIOTest {
        private static final int PORT = 9090;
        private static final int BACK_LOG = 2;
    
        public static void main(String[] args) {
            ServerSocket server = null;
            try {
                server = new ServerSocket();
                server.bind(new InetSocketAddress(PORT), BACK_LOG);
                System.out.println("server started , port : " + PORT);
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                // 接受客户端连接
                while (true) {
                    // 先阻塞,这样客户端暂时无法连接进来
    //                System.in.read();
    
                    // 这个方法也是阻塞的,如果没有客户端连接进来,会一直阻塞在这里,除非设置了超时时间
                    Socket client = server.accept();
    
                    System.out.println("client " + client.getPort() + " connected!!!");
                    // 客户端连接进来后,开辟一个新的线程去接收并处理
                    new Thread(() -> {
                        try {
                            InputStream inputStream = client.getInputStream();
                            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                            char[] data = new char[1024];
                            while (true) {
                                int num = reader.read(data);
                                if (num > 0) {
                                    System.out.println("client read some data is :" + num + " val :" + new String(data, 0, num));
                                } else if (num == 0) {
                                    System.out.println("client read nothing!");
                                    continue;
                                } else {
                                    System.out.println("client read -1...");
                                    System.in.read();
                                    client.close();
                                    break;
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }).start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    并且在Linux实例上运行这个代码,然后在自己本地的机器上准备客户端的代码:

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.SocketChannel;
    import java.util.LinkedList;
    
    
    public class C10Kclient {
    
        public static void main(String[] args) {
            LinkedList<SocketChannel> clients = new LinkedList<>();
            InetSocketAddress serverAddr = new InetSocketAddress("192.168.205.138", 9090);
            for (int i = 10000,j = 10001; i < 65000; i+=2,j+=2) {
                try {
                    SocketChannel client1 = SocketChannel.open();
                    SocketChannel client2 = SocketChannel.open();
                    client1.bind(new InetSocketAddress("192.168.205.1", i )); 
                    client1.connect(serverAddr);
                    clients.add(client1);
    
                    client2.bind(new InetSocketAddress("192.168.205.1", j));
                    
                    client2.connect(serverAddr);
                    clients.add(client2);
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("clients "+ clients.size());
            try {
                System.in.read();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    服务端每次用两端口循环多次去和服务端建立连接,我们可以观察服务端建立连接的速度,通过服务端打印的信息可以感知到连接的速度。

    [root@io socket]# javac SocketServerBIOTest.java && java SocketServerBIOTest
    server started , port : 9090
    client 10000 connected!!!
    client 10001 connected!!!
    client 10002 connected!!!
    client 10003 connected!!!
    client 10006 connected!!!
    client 10007 connected!!!
    client 10008 connected!!!
    client 10004 connected!!!
    client 10009 connected!!!
    client 10010 connected!!!
    client 10011 connected!!!
    ...
    

    NIO

    我们把服务端的BIO切换成NIO,服务端的代码改成如下:

    import java.net.InetSocketAddress; 
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.LinkedList;
    
    public class SocketServerNIOTest {
    
    
        public static void main(String[] args) throws Exception {
    
            LinkedList<SocketChannel> clients = new LinkedList<>();
    
            ServerSocketChannel ss = ServerSocketChannel.open();  //服务端开启监听:接受客户端
            ss.bind(new InetSocketAddress(9090));
            ss.configureBlocking(false); 
    
            while (true) {
            	
                SocketChannel client = ss.accept();
                if (client == null) {
                 
                } else {
                    client.configureBlocking(false); //重点  socket(服务端的listen socket<连接请求三次握手后,往我这里扔,我去通过accept 得到  连接的socket>,连接socket<连接后的数据读写使用的> )
                    int port = client.socket().getPort();
                    System.out.println("client..port: " + port);
                    clients.add(client);
                }
    
                ByteBuffer buffer = ByteBuffer.allocateDirect(4096); 
                //遍历已经链接进来的客户端能不能读写数据
                for (SocketChannel c : clients) {  
                    int num = c.read(buffer);  
                    if (num > 0) {
                        buffer.flip();
                        byte[] aaa = new byte[buffer.limit()];
                        buffer.get(aaa);
    
                        String b = new String(aaa);
                        System.out.println(c.socket().getPort() + " : " + b);
                        buffer.clear();
                    }
                }
            }
        }
    }
    

    其中

    ss.configureBlocking(false); 
    

    即把服务端设置为非阻塞的,由于非阻塞,所以死循环中,代码不会卡在:

    SocketChannel client = ss.accept();
    

    这里一直不执行,而且也无须抛出一个新的线程去接收客户端。

    当得到

    client != null
    

    的时候,即有新的客户端连接进来,我们把这个clients加入到列表中,然后遍历clients,去消费客户端的请求。

    同时,我们可以在服务端设置客户端的非阻塞,即:

    client.configureBlocking(false);
    

    再次运行客户端,并切换到服务端查看打印日志,速度比前面的BIO快了非常多。

    [root@io socket]# javac SocketServerNIOTest.java && java SocketServerNIOTest
    ... 速度快很多...
    client..port: 10000
    client..port: 10001
    client..port: 10002
    client..port: 10003
    client..port: 10004
    client..port: 10005
    client..port: 10006
    client..port: 10007
    client..port: 10008
    client..port: 10009
    client..port: 10010
    client..port: 10011
    ...
    

    为什么BIO慢

    因为每次连接都会发生两次系统调用,一次是通过accept建立socket,另一次是调用clone方法抛出一个线程。
    而NIO只有一个线程(线程克隆的耗时就不存在了),而且操作系统也提供了对应的支持。所以要比BIO快很多。

    源码

    Github---
    title: Java IO学习笔记五:BIO到NIO
    tags:

    • Java
    • IO
      categories:
    • ["技术基础","编程语言","Java"]
      abbrlink: 7a01e21
      date: 2021-06-16 20:08:00

    作者:Grey

    原文地址: Java IO学习笔记五:BIO到NIO

    准备环境

    准备一个CentOS7的Linux实例:
    实例的IP:
    192.168.205.138

    我们这次实验的目的就是直观感受一下Socket编程中BIO模型和NIO模型的性能差异

    BIO

    准备服务端代码:

    import java.io.*;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * BIO Socket Server
     */
    public class SocketServerBIOTest {
        private static final int PORT = 9090;
        private static final int BACK_LOG = 2;
    
        public static void main(String[] args) {
            ServerSocket server = null;
            try {
                server = new ServerSocket();
                server.bind(new InetSocketAddress(PORT), BACK_LOG);
                System.out.println("server started , port : " + PORT);
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                // 接受客户端连接
                while (true) {
                    // 先阻塞,这样客户端暂时无法连接进来
    //                System.in.read();
    
                    // 这个方法也是阻塞的,如果没有客户端连接进来,会一直阻塞在这里,除非设置了超时时间
                    Socket client = server.accept();
    
                    System.out.println("client " + client.getPort() + " connected!!!");
                    // 客户端连接进来后,开辟一个新的线程去接收并处理
                    new Thread(() -> {
                        try {
                            InputStream inputStream = client.getInputStream();
                            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                            char[] data = new char[1024];
                            while (true) {
                                int num = reader.read(data);
                                if (num > 0) {
                                    System.out.println("client read some data is :" + num + " val :" + new String(data, 0, num));
                                } else if (num == 0) {
                                    System.out.println("client read nothing!");
                                    continue;
                                } else {
                                    System.out.println("client read -1...");
                                    System.in.read();
                                    client.close();
                                    break;
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }).start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    并且在Linux实例上运行这个代码,然后在自己本地的机器上准备客户端的代码:

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.SocketChannel;
    import java.util.LinkedList;
    
    
    public class C10Kclient {
    
        public static void main(String[] args) {
            LinkedList<SocketChannel> clients = new LinkedList<>();
            InetSocketAddress serverAddr = new InetSocketAddress("192.168.205.138", 9090);
            for (int i = 10000,j = 10001; i < 65000; i+=2,j+=2) {
                try {
                    SocketChannel client1 = SocketChannel.open();
                    SocketChannel client2 = SocketChannel.open();
                    client1.bind(new InetSocketAddress("192.168.205.1", i )); 
                    client1.connect(serverAddr);
                    clients.add(client1);
    
                    client2.bind(new InetSocketAddress("192.168.205.1", j));
                    
                    client2.connect(serverAddr);
                    clients.add(client2);
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("clients "+ clients.size());
            try {
                System.in.read();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    服务端每次用两端口循环多次去和服务端建立连接,我们可以观察服务端建立连接的速度,通过服务端打印的信息可以感知到连接的速度。

    [root@io socket]# javac SocketServerBIOTest.java && java SocketServerBIOTest
    server started , port : 9090
    client 10000 connected!!!
    client 10001 connected!!!
    client 10002 connected!!!
    client 10003 connected!!!
    client 10006 connected!!!
    client 10007 connected!!!
    client 10008 connected!!!
    client 10004 connected!!!
    client 10009 connected!!!
    client 10010 connected!!!
    client 10011 connected!!!
    ...
    

    NIO

    我们把服务端的BIO切换成NIO,服务端的代码改成如下:

    import java.net.InetSocketAddress; 
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.LinkedList;
    
    public class SocketServerNIOTest {
    
    
        public static void main(String[] args) throws Exception {
    
            LinkedList<SocketChannel> clients = new LinkedList<>();
    
            ServerSocketChannel ss = ServerSocketChannel.open();  //服务端开启监听:接受客户端
            ss.bind(new InetSocketAddress(9090));
            ss.configureBlocking(false); 
    
            while (true) {
            	
                SocketChannel client = ss.accept();
                if (client == null) {
                 
                } else {
                    client.configureBlocking(false); //重点  socket(服务端的listen socket<连接请求三次握手后,往我这里扔,我去通过accept 得到  连接的socket>,连接socket<连接后的数据读写使用的> )
                    int port = client.socket().getPort();
                    System.out.println("client..port: " + port);
                    clients.add(client);
                }
    
                ByteBuffer buffer = ByteBuffer.allocateDirect(4096); 
                //遍历已经链接进来的客户端能不能读写数据
                for (SocketChannel c : clients) {  
                    int num = c.read(buffer);  
                    if (num > 0) {
                        buffer.flip();
                        byte[] aaa = new byte[buffer.limit()];
                        buffer.get(aaa);
    
                        String b = new String(aaa);
                        System.out.println(c.socket().getPort() + " : " + b);
                        buffer.clear();
                    }
                }
            }
        }
    }
    

    其中

    ss.configureBlocking(false); 
    

    即把服务端设置为非阻塞的,由于非阻塞,所以死循环中,代码不会卡在:

    SocketChannel client = ss.accept();
    

    这里一直不执行,而且也无须抛出一个新的线程去接收客户端。

    当得到

    client != null
    

    的时候,即有新的客户端连接进来,我们把这个clients加入到列表中,然后遍历clients,去消费客户端的请求。

    同时,我们可以在服务端设置客户端的非阻塞,即:

    client.configureBlocking(false);
    

    再次运行客户端,并切换到服务端查看打印日志,速度比前面的BIO快了非常多。

    [root@io socket]# javac SocketServerNIOTest.java && java SocketServerNIOTest
    ... 速度快很多...
    client..port: 10000
    client..port: 10001
    client..port: 10002
    client..port: 10003
    client..port: 10004
    client..port: 10005
    client..port: 10006
    client..port: 10007
    client..port: 10008
    client..port: 10009
    client..port: 10010
    client..port: 10011
    ...
    

    为什么BIO慢

    因为每次连接都会发生两次系统调用,一次是通过accept建立socket,另一次是调用clone方法抛出一个线程。
    而NIO只有一个线程(线程克隆的耗时就不存在了),而且操作系统也提供了对应的支持。所以要比BIO快很多。

    源码

    Github

  • 相关阅读:
    【Go】windows下搭建go语言编译环境
    【java回调】同步/异步回调机制的原理和使用方法
    【tomcat】tomcat远程调试
    【tomcat】获取访问者真实IP
    【深度学习学习记录】之一:开篇闲扯一些话
    【java】线程安全的整型类AtomicInteger
    【OpenStack】源码级深入了解删除虚拟机操作
    【Maven】maven的常用命令以及搭建maven私人仓库
    素 数 (第三届省赛)
    房间安排(第三届省赛)
  • 原文地址:https://www.cnblogs.com/greyzeng/p/14885134.html
Copyright © 2011-2022 走看看