IO简介
IO即为Input/Output,IO可分为两种磁盘IO和网络IO。顾名思义,磁盘IO指的是从磁盘中进行输入和输出,也就是磁盘数据的读取和存储;而网络IO便指的是网络数据的读取和写入,具体的数据读取方式则是前文提到的JAVA网络编程之Socket。本系列主要内容为java网络编程IO系列,涵盖java中的BIO、NIO和AIO三种模型。对于IO模型不太了解的建议先看一下Linux下的五种网络IO模型
BIO概念
起初,java中的IO操作,都是基于BIO的,BIO原意为Base IO ,因为是最早使用的,也是IO调用的基础用法,但因为BIO会阻塞当前线程B又被理解为Block,即阻塞IO,也是现在通用的说法。传统BIO执行时,执行方式为同步阻塞的,当前线程会等待IO调用执行完成,返回结果,这个会阻塞当前线程,此时cpu会保存当前线程上下文,进行线程切换,执行其他就绪线程。线程切换是需要耗费cpu资源的,频繁的切换线程会导致cpu资源的极大浪费。
BIO处理流程
为了支持并发,服务器通常会采用多线程并发处理请求。 即有新连接进入时,创建新线程,在新线程中进行IO和计算处理。过程如下所示: 通常为了避免频繁创建和销毁线程,服务器会使用线程池进行线程调度和管理。
BIO实现
public class BIOServer {
static ExecutorService serverService
= Executors
.newFixedThreadPool(10);
public static void main(String
[] args
) throws IOException
{
ServerSocket serverSocket
= new ServerSocket(9090);
while (true) {
Socket socket
= serverSocket
.accept();
serverService
.submit(new ServerRequestHandler(socket
));
}
}
}
public class ServerRequestHandler implements Runnable {
private Socket socket
;
public ServerRequestHandler(Socket socket
) {
this.socket
= socket
;
}
public void run() {
try {
DataInputStream dis
= new DataInputStream(socket
.getInputStream());
System
.out
.println("收到消息:" +dis
.readUTF());
DataOutputStream dos
=new DataOutputStream(socket
.getOutputStream());
dos
.writeUTF("消息已收到");
socket
.close();
} catch (IOException e
) {
e
.printStackTrace();
}
}
}
public class Client {
public static void main(String
[] args
) throws IOException
{
for (int i
=1;i
<5;i
++) {
Socket socket
= new Socket("localhost", 9090);
DataOutputStream dos
= new DataOutputStream(socket
.getOutputStream());
dos
.writeUTF("这是来自客户端" + i
+ "的请求");
DataInputStream dis
=new DataInputStream(socket
.getInputStream());
System
.out
.println("服务端响应:"+dis
.readUTF());
socket
.close();
}
}
}
总结
从处理流程图及简易代码实现可以看出,一个连接会分配一个线程处理,而我们的线程资源是异常宝贵的,不可能无限多,这样一来线程数就限制了服务器的并发。当线程内的请求处理的越慢,服务端可处理的并发请求就越低。而在线程内进行数据的读取和写入IO操作又是阻塞的,进而使得单个线程处理请求的时间拉长,最终导致服务器的整体并发能力不足。当需要满足更高并发的要求时,就要使用其他的IO模式了。