Java网络编程——BIO阻塞IO

Posted 胡玉洋 

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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();
        
    

客户端:

@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();
        
    

  • 以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();
        
    

  • 以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》

以上是关于Java网络编程——BIO阻塞IO的主要内容,如果未能解决你的问题,请参考以下文章

Java网络编程——BIO阻塞IO

Java网络编程——BIO阻塞IO

网络IO模型——简析NIO / BIO / AIO

网络IO模型——简析NIO / BIO / AIO

网络IO模型——简析NIO / BIO / AIO

Java IO编程全解——传统的BIO编程