BIO
JAVA BIO
(blocking I/O
):同步并阻塞(传统阻塞型),服务器实现模型为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制完善(实现多个客户连接服务器)
应用场景
BIO
方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4
以前的唯一选择,但程序简单易理解。
BIO
工作原理大致流程图如下:
- 服务器启动一个
ServreSocket
; - 客户端启动
Socket
对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与之通讯; - 客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝;
- 如果有响应,客户端线程会等待请求结束后,在继续执行;
JAVA代码示例:
package com.kelecc.bio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
* 功能描述: BIO Demo
*
* @Author keLe
* @Date 2022/1/24
*/
public class BIOServer {
public static void main(String[] args) {
//创建一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
try {
//创建ServerSocket ,监听8888端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务启动");
while(true){
//监听,等待客户端链接,该方法阻塞,直到建立连接。
System.out.println("等待连接");
//main方法的主线程
System.out.println("线程信息 id = " + Thread.currentThread().getId());
System.out.println("线程名字 name="+Thread.currentThread().getName());
final Socket socket = serverSocket.accept();
System.out.println("连接到客户端");
//创建一个线程,与之通讯
executorService.execute(()->{
handler(socket);
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void handler(Socket socket){
byte[] bytes = new byte[1024];
try {
//得到输入流
InputStream inputStream = socket.getInputStream();
//循环读取客户端发送的数据
while(true){
System.out.println("线程信息 id = " + Thread.currentThread().getId());
System.out.println("线程名字 name="+Thread.currentThread().getName());
System.out.println("read.......");
int read = inputStream.read(bytes);
if(read != -1){
//控制台打印输出
System.out.println("输出客户端发送的数据"+new String(bytes,0,read));
}else{
//读取完毕
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("关闭客户端连接");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 代码运行:
只要没有客户端连接,线程都会阻塞,socket.accept
调用之前:
final Socket socket = serverSocket.accept();
- 使用
CMD
创建一个客户端连接:使用命令测试8888端口
telnet 127.0.0.1 8888
- 连接成功:
- 控制台打印:
-
观察打印输出,我们发现:
主线程
main
, 在有客户端连接过后,又会阻塞在 客户端连接进来之前,所以这里会打印 两个等待连接;我们也可以看上面的流程图 一目了然;
serverSocket.accept();
而我们客户端连接的子线程 通过我们设计代码连接成功后,当我们没有发送数据,子线程会阻塞在 read/write
这个操作时;可以看上面的流程图;
int read = inputStream.read(bytes);
- 发送数据,在当前界面,按住
ctrl+]
键;
- 通过
send
发送数据;
- 观测输出日志;
发送数据成功之后,子线程依旧阻塞在 read
操作之前
接下来,我们新建第二个客户端,启动第二个cmd
,观察输出日志;
总结:
- 每个请求都需要创建独立的线程,与对应的客户端进行数据
Read
,业务处理,数据Write
; - 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大;
- 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在Read操作上,造成线程资源浪费;