- 阻塞式IO模型,BIO
JDK1.4 之前都是采用BIO模式(blocking I/O) ,阻塞式IO,模型如图
解释: 应用程序需要从磁盘读取数据分为两个阶段,1将磁盘数据复制到内核,2将内核数据复制到应用程序空间:
- 准备数据:应用程序问cpu说:我需要一个aaa.txt 文件,你去给我取来。CPU告诉应用程序,你等着我去给你准备数据(应用程序一直傻傻等待)。CPU转身问磁盘:磁盘我需要aaa.txt 文件,磁盘开始准备数据,并将数据复制到内核空间中(耗时)
- 等磁盘把数据给到CPU之后,告诉应用程序数据准备好了,现在给你,CPU将数据从内核复制到用户空间(耗时)
在上述两个阶段中,对于应用程序的一个线程来说发出读取文件指令后,这个线程都是一直在等待(阻塞),傻傻什么都不做,如果要想处理其他事情就需要其他线程来做。很明显这样的BIO模型性能很低,且消耗资源
jdk中java.io.* 包中都是基于阻塞式io实现的
代码:
public class BioMain {
public static void main(String[] args) throws IOException {
//服务端创建
ServerSocket serverSocket = new ServerSocket(1234);
System.out.println("服务端启动,等待连接");
//可以一直接收客户端连接,但是只能有一个客户端可以读取数据
while (true) {
//accept阻塞,serverSocket等待客户端的连接
Socket accept = serverSocket.accept();
System.out.println("来自客户端的连接:" + accept.getRemoteSocketAddress());
InputStream inputStream = accept.getInputStream();
Scanner scanner = new Scanner(inputStream);
//针对一个客户端socket连接进行IO操作
while (true) {
String line = scanner.nextLine();//阻塞,一直等待数据进来
if ("q".equals(line)){
break;
}
System.out.println("收到数据:" + line);
accept.getOutputStream().write("I am server ask:".getBytes());
}
}
}
}
上述代码中只有一个main 线程,也就只能处理一个连接的请求,如果想处理多个请求,我们可以在服务端采用多线程的方式,也就对每个socket连接都分配一个线程
代码2:
public class BioMain2 {
public static void main(String[] args) throws IOException {
//服务端创建
ServerSocket serverSocket = new ServerSocket(1234);
System.out.println("服务端启动,等待连接");
while (true) {
//accept阻塞,serverSocket等待客户端的连接
Socket accept = serverSocket.accept();
System.out.println(Thread.currentThread().getName() + ":来自客户端的连接:" + accept.getRemoteSocketAddress());
new Thread(new Runnable() {//实际应用的时候替换成线程池
@Override
public void run() {
try {
InputStream inputStream = accept.getInputStream();
Scanner scanner = new Scanner(inputStream);
//针对一个客户端socket连接进行IO操作
while (true) {
String line = scanner.nextLine();//阻塞,一直等待数据进来
if ("q".equals(line)) {
break;
}
System.out.println(Thread.currentThread().getName() +"收到数据:" + line);
accept.getOutputStream().write("I am server ask:".getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
- 非阻塞式IO模型 NIO,Non-Blocking IO
模型如图,应用进程向内核发出读取文件指令之后,内核就向磁盘发送指令准备数据去了,而应用进程不会阻塞等待去干其他事情了,过一会儿问问数据准备好了没,如果内核数据还没准备好,应用进程就继续干其他事情,一会儿一问数据好了没,这段时间中数据会从磁盘复制到内核空间,复制完成之后,待到应用进程再次询问的时候,内核就告诉它数据已经准备好了,在这个时候数据其实还是没有真正的进入到应用空间,此时应用进程又进入阻塞,等待数据从内核空间复制到应用空间,只不过这个阶段要比前一个阶段时间要短的多。总结就是第一个阶段不阻塞,第二个阶段阻塞
非阻塞式IO在Java中,jdk1.4以后有了一个java.nio.* 的包,Java中的nio实现其实是基于非阻塞式IO和多路复用IO模型实现的
- IO复用模型
由于并不是所有的socket连接都要立马执行任务,IO复用模型,把所有的客户端连接都暂存到一个Map集合中,然后不断的循环遍历看哪些socket需要执行任务,就把它从map集合中获取处理进行处理
代码3:
public class NioMain {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//设置为非阻塞方式
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(1234));
System.out.println("NIO 服务端开启成功。。。");
Selector selector = Selector.open();
//注册监听的事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//缓存区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true){
//循环监听客户端
int select = selector.select();
if (select==0){
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
SocketChannel accept = channel.accept();
System.out.println("接收到客户端请求:"+accept.getRemoteAddress());
accept.configureBlocking(false);
//将客户端过来的channel设置成读取状态
accept.register(selector,SelectionKey.OP_READ);
}
if (selectionKey.isReadable()){
SocketChannel channel = (SocketChannel) selectionKey.channel();
//通过channel将数据读取到缓存中
channel.read(buffer);
String str = new String(buffer.array());
buffer.clear();
System.out.println("接收到数据:"+str+"——来自:"+channel.getRemoteAddress());
//回写数据
channel.write(ByteBuffer.wrap("I am form Nio Server".getBytes()));
}
iterator.remove();
}
}
}
}
推荐:https://www.cnblogs.com/Jack-Blog/p/11991240.html