• Java网络编程——BIO阻塞IO


    BIO(Blocking IO)也就是阻塞IO,当服务端和客户端交互时,如果服务端接收了一个客户端请求,就要为这个客户端一直服务直到结束,否则无法为下一个客户端服务。BIO就属于同步阻塞IO。


    BIO单线程处理请求

    BIO服务器端:

    @Slf4j
    public class BIOServer {
        @SneakyThrows
        public static void main(String[] args) {
            ServerSocket serverSocket=new ServerSocket();
            try {
                serverSocket.bind(new InetSocketAddress("127.0.0.1",8080),50);
                log.info("server started.");
                while (true){
                    Socket socket=serverSocket.accept(); // 如果没有客户端连接,会阻塞在这里,直到客户端发送了连接
                    log.info("receive connection from client. client:{}",socket.getRemoteSocketAddress());
                    byte[] buffer=new byte[64];
                    socket.getInputStream().read(buffer); // 如果没有读取到当前客户端发送的数据,会阻塞在这里,直到客户端发送了数据
                    log.info("receive message from client. client:{} message:{}",socket.getRemoteSocketAddress(),new String(buffer,"UTF-8"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                serverSocket.close();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    客户端:

    @Slf4j
    public class BIOClient {
        @SneakyThrows
        public static void main(String[] args) {
            Socket socket = new Socket();
            try {
                socket.connect(new InetSocketAddress("127.0.0.1", 8080));
                log.info("client connect finished");
                socket.getOutputStream().write("hello".getBytes(StandardCharsets.UTF_8));
                log.info("client send finished");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                socket.close();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 以Run模式启动服务端
    • 在客户端的 socket.getOutputStream().write("hello".getBytes(StandardCharsets.UTF_8)); 处打上断点,以Debug模式运行一个客户端A,执行到断点时,服务端已经接收到客户端A的请求(在控制台打印了 receive connection from client. client:/127.0.0.1:61501
    • 再以Debug模式运行一个客户端B,服务端没反应,因为这时客户端A还没发送数据,所以服务端目前是在 socket.getInputStream().read(buffer); 的地方阻塞了(还在等着接收客户端A发送数据)
    • 再以Debug模式运行一个客户端C,服务端同样没反应
    • 让客户端A继续运行完,发现服务端读取到客户端A的数据(打印了receive message from client. client:/127.0.0.1:61501 message:hello )后,才能接收到客户端B的连接(打印了receive connection from client. client:/127.0.0.1:61697
    • 让客户端B继续运行完,发现服务端读取到客户端B的数据(打印了receive message from client. client:/127.0.0.1:61697 message:hello )后,才能接收到客户端C的连接(打印了receive connection from client. client:/127.0.0.1:61701

    BIO的“阻塞”就体现在这里,当一个服务端线程正在处理或者等待处理某个客户端的请求,是无法为其他客户端服务的。


    BIO多线程处理请求

    假如当某个客户端连接上服务端后,不写数据或写数据比较慢,其他客户端的请求就不能被及时处理,这时可以通过多线程的方式来解决,每当收到一个新的客户端时,就单独让一个线程专门处理这个客户端的请求,如下:

    @Slf4j
    public class BIOMultipleServer {
        private final static ExecutorService threadPool= Executors.newCachedThreadPool();
        @SneakyThrows
        public static void main(String[] args) {
            ServerSocket serverSocket=new ServerSocket();
            try {
                serverSocket.bind(new InetSocketAddress("127.0.0.1",8080),50);
                log.info("server started.");
                while (true){
                    Socket socket=serverSocket.accept();
                    log.info("receive connection from client. client:{}",socket.getRemoteSocketAddress());
                    threadPool.submit(new Runnable() {
                        @SneakyThrows
                        @Override
                        public void run() {
                            byte[] buffer=new byte[64];
                            socket.getInputStream().read(buffer);
                            log.info("receive message from client. client:{} message:{}",socket.getRemoteSocketAddress(),new String(buffer,"UTF-8"));
                        }
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                serverSocket.close();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 以Run模式启动服务端
    • 在客户端的 socket.getOutputStream().write("hello".getBytes(StandardCharsets.UTF_8)); 处打上断点,以Debug模式运行一个客户端A,执行到断点时,服务端已经接收到客户端A的请求(在控制台打印了 receive connection from client. client:/127.0.0.1:61517
    • 再以Debug模式运行一个客户端B,执行到断点时,服务端已经接收到客户端B的请求(在控制台打印了 receive connection from client. client:/127.0.0.1:61521
    • 再以Debug模式运行一个客户端C,执行到断点时,服务端已经接收到客户端C的请求(在控制台打印了 receive connection from client. client:/127.0.0.1:61525
    • 让客户端A、B、C继续运行完,服务端也能正常接收消息

    这样就可以通过多线程的方式解决服务端不能同时为多个客户端服务的弊端。但又带来了新的问题:每接收一个客户端就用一个线程去处理,如果创建的线程过多,会消耗大量的服务器资源,即使用线程池的方式来限制线程数量和上下文切换,如果多个客户端连接成功后都等待,也会导致服务端的线程都阻塞,治标不治本。因此BIO只适合连接数较少的场景,当连接数较多时,Java NIO的优势就体现出来了。



    转载请注明出处——胡玉洋 《Java网络编程——BIO阻塞IO》

  • 相关阅读:
    安装Jupyter可能会出现的问题
    Servlet 学习笔记(一)
    [ACNOI2022]不猜不行
    说说你对vue的mixin的理解,有什么应用场景
    传统社区如何进行数字化转型?快鲸智慧社区解决方案为你支招
    上帝之眼Nmap简介及命令大全
    DoozyUI⭐️十四、UIView:UI容器
    基于DOTween插件实现金币飞行到指定位置功能
    网络爬虫——urllib(4)文末好书推荐
    网络安全 - ARP 欺骗原理+实验
  • 原文地址:https://blog.csdn.net/huyuyang6688/article/details/126051312