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();
}
}
}
socket.getOutputStream().write("hello".getBytes(StandardCharsets.UTF_8));
处打上断点,以Debug模式运行一个客户端A,执行到断点时,服务端已经接收到客户端A的请求(在控制台打印了 receive connection from client. client:/127.0.0.1:61501
)socket.getInputStream().read(buffer);
的地方阻塞了(还在等着接收客户端A发送数据)receive message from client. client:/127.0.0.1:61501 message:hello
)后,才能接收到客户端B的连接(打印了receive connection from client. client:/127.0.0.1:61697
)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();
}
}
}
socket.getOutputStream().write("hello".getBytes(StandardCharsets.UTF_8));
处打上断点,以Debug模式运行一个客户端A,执行到断点时,服务端已经接收到客户端A的请求(在控制台打印了 receive connection from client. client:/127.0.0.1:61517
)receive connection from client. client:/127.0.0.1:61521
)receive connection from client. client:/127.0.0.1:61525
)这样就可以通过多线程的方式解决服务端不能同时为多个客户端服务的弊端。但又带来了新的问题:每接收一个客户端就用一个线程去处理,如果创建的线程过多,会消耗大量的服务器资源,即使用线程池的方式来限制线程数量和上下文切换,如果多个客户端连接成功后都等待,也会导致服务端的线程都阻塞,治标不治本。因此BIO只适合连接数较少的场景,当连接数较多时,Java NIO的优势就体现出来了。
转载请注明出处——胡玉洋 《Java网络编程——BIO阻塞IO》