最近在跟james和tony老师学习JAVA,现在讲到了BIO阻塞式网络编程。在此谈一谈自己学完之后的理解。
BIO-阻塞IO的含义
同步异步生活案例:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
阻塞非阻塞例子:
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。
同步异步和阻塞非阻塞结合起来的例子:
假如你要办理银行业务
同步阻塞;首先你来到银行去登记了你要办理的业务,然后银行的人说,注意看那个屏幕,到你就来,不要离开这里,不要做其它的事情
同步非阻塞:首先你来到银行去登记了你要办理的业务,然后银行的人说,注意看那个屏幕,到你就来,不要离开这里,于是你一直不停的看到你没,这时来了个电话,你边打电话边看
异步阻塞;首先你来到银行去登记了你要办理的业务,然后银行的人说,到你了就通知你,你回去吧,但是你回去了什么事情都别做
异步非阻塞:超级vip要办理业务,来银行说,我要办理什么业务,银行说好,我们办好了把结果反馈给你,你说好,于是走了,出去吃饭去了。
先用BIO实现一个简单功能:
server端:监听,打印客户端发送过来的内容。
客户端:向服务端发送内容。
服务端代码:
public class BIOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务器启动成功");
while (!serverSocket.isClosed()) {
Socket request = serverSocket.accept();// 阻塞
System.out.println("收到新连接 : " + request.toString());
try {
// 接收数据、打印
InputStream inputStream = request.getInputStream(); // net + i/o
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String msg;
while ((msg = reader.readLine()) != null) { // 没有数据,阻塞
if (msg.length() == 0) {
break;
}
System.out.println(msg);
}
System.out.println("收到数据,来自:"+ request.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
request.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
serverSocket.close();
}
}
客户端代码:
public class BIOClient {
private static Charset charset = Charset.forName("UTF-8");
public static void main(String[] args) throws Exception {
Socket s = new Socket("localhost", 8080);
OutputStream out = s.getOutputStream();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入:");
String msg = scanner.nextLine();
out.write(msg.getBytes(charset)); // 阻塞,写完成
scanner.close();
s.close();
}
}
这种写法是一个服务线程为多个客户端服务。服务端执行了socket = server.accept()后服务端才能与客户端建立连接,否则客户端一直阻塞等待连接建立,server端没有阻塞在server.accept方法时客户端如果请求连接就会报connection refused异常。当服务端接受了一个客户端的连接就开始为客户端服务,服务完成后再继续调用socket = server.accept()并接待下一个客户端。缺点很明显,只要服务端还没有处理完上一个客户端的请求,别的客户端的请求就必须要先阻塞在那里等待。就好像是有一个售票口只有一个售票员,而在外面有一堆等着买票的人一样,只有一个人买到票并离开了,售票员才能为下一个人服务。
升级一下程序,使每个客户端都有一个服务线程为其服务:
服务端代码:
// 多线程支持
public class BIOServer1 {
private static ExecutorService threadPool = Executors.newCachedThreadPool();
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("tomcat 服务器启动成功");
while (!serverSocket.isClosed()) {
Socket request = serverSocket.accept();
System.out.println("收到新连接 : " + request.toString());
threadPool.execute(() -> {
try {
// 接收数据、打印
InputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String msg;
while ((msg = reader.readLine()) != null) { // 阻塞
if (msg.length() == 0) {
break;
}
System.out.println(msg);
}
System.out.println("收到数据,来自:"+ request.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
request.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
serverSocket.close();
}
}
通过上面的程序我们可以看到bio的api比较简单,适用于连接数量比较少的架构。即使是使用了多线程去处理建立连接后的操作,但是由于bio程序在read和write时都会阻塞线程,直到有数据可读或可写,对线程资源造成了极大的浪费,所以如果并发要求比较高的话,bio可能不是很好的选择,可以使用NIO进行改进。