BIO和NIO的基本用法和API讲解
1 BIO
可以理解为Blocking IO 是同步阻塞的IO,也就是说,当有多个请求过来的时候,请求会呈现为链状结构,遵循先进先出的原则

1.1 单线程版本
1.1.1 服务端
//服务端单线程处理
public class BioServer { public static void main(String[] args) throws IOException, InterruptedException {
// 1.创建服务端socket
ServerSocket serverSocket = new ServerSocket(9000);
System.out.println("服务端启动...");
while (true){
System.out.println("服务端接受客户端连接前");
// 2.这里会阻塞
Socket socket = serverSocket.accept();
System.out.println("服务端接受客户端连接后");
// 3.单线程处理方案
handel(socket);
}
}
private static void handel(Socket socket) throws IOException, InterruptedException {
byte[] bytes = new byte[1024];
System.out.println("服务端读取客户端传入信息前" );
// 3.1 read会阻塞 读取客户端数据,要客户端开始写才会向下执行
int read = socket.getInputStream().read(bytes);
if(read != -1){
System.out.println( "服务端读取客户端传入信息,msg:"+new String(bytes,0,read) );
}
System.out.println("服务端向客户端写入信息" );
Thread.sleep(200); //假设写需要200ms
socket.getOutputStream().write("hello client".getBytes());
socket.getOutputStream().flush();
socket.close();
} }
1.1.2 客户端
package com.ruoyi.weixin.Test.SI_BIO; import java.io.IOException;
import java.net.Socket; public class BioClient { public static void main(String[] args) throws IOException {
for (int i = 0; i < 3; i++) { new Thread(()->{
try {
connect(Thread.currentThread().getName());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}, "客户端"+i ).start();
}
} public static void connect(String i) throws IOException, InterruptedException {
String clientname = Thread.currentThread().getName();
// 1.创建连接绑定ip和端口
Socket socket = new Socket("localhost", 9000);
System.out.println(clientname + "开始向服务端写入消息" );
String msg = clientname + "-hello server";
Thread.sleep(300); //假设写需要300ms
socket.getOutputStream().write(msg.getBytes());
socket.getOutputStream().flush();
System.out.println(clientname + "开始向服务端写入消息完成" ); byte[] bytes = new byte[1024];
// 2.read会阻塞 读取客户端数据,要服务端开始写才会向下执行
int read = socket.getInputStream().read(bytes);
// 3.接收服务端回传的数据
System.out.println(clientname + "接收到服务端的数据:" + new String(bytes,0,read) );
socket.close();
} }
1.1.3 执行
在客户端socket.getOutputStream().write(msg.getBytes());这里打个断点
在服务端socket.getOutputStream().write("hello client".getBytes());打个断点
1)启动服务端

2)启动客户端
客户端控制台,在断点处停住了

服务端控制台,由于客户端在写之前停住了,所以在read()这里阻塞了

客户端放开断点
客户端控制台:
向服务端写完数据后,执行到read,阻塞等待服务端的数据

服务端控制台:
读取完客户端的数据,向下执行
由于断点,在向客户端写之前停住了

放开服务端断点
服务端控制台:
向客户端写完数据,请求处理完成,继续等待下一个请求

客户端控制台,接收服务端数据,请求完成

通过上面的示例,Nio处理请求是一个一个处理的,也就是同步
数据交互read()是阻塞的,需要等待write的执行,也就是阻塞
1.2 多线程版本
上面是一个线程去处理所有请求,现在,一个请求来了,就开一个线程去处理。
优点:把read阻塞给优化了,这里不会阻塞其他线程了,只会在自己的线程里面阻塞,提高了并发能力,提高了效率
缺点:这里有可能会无限制创建线程,线程是稀有资源,如果请求很多,这个时候客户端一直不执行write方法,所有线程就会阻塞在read方法那里,导致线程暴涨,cpu升高,导致机器假死
再优化,采用线程池管理线程去处理请求,可以限制线程数量
public class BioServer {
public static void main(String[] args) throws IOException, InterruptedException {
// 1.创建服务端socket
ServerSocket serverSocket = new ServerSocket(9000);
System.out.println("服务端启动...");
while (true){
System.out.println("服务端接受客户端连接前");
// 2.这里会阻塞
Socket socket = serverSocket.accept();
System.out.println("服务端接受客户端连接后");
// 3.单线程处理方案
new Thread(()->{
try {
handel(socket);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
private static void handel(Socket socket) throws IOException, InterruptedException {
byte[] bytes = new byte[1024];
System.out.println("服务端读取客户端传入信息前" );
// 3.1 read会阻塞 读取客户端数据,要客户端开始写才会向下执行
int read = socket.getInputStream().read(bytes);
if(read != -1){
System.out.println( "服务端读取客户端传入信息,msg:"+new String(bytes,0,read) );
}
System.out.println("服务端向客户端写入信息" );
Thread.sleep(200); //假设写需要200ms
socket.getOutputStream().write("hello client".getBytes());
socket.getOutputStream().flush();
socket.close();
}
}
2 NIO
我们上面对BIO进行了一系列的说明,证明了BIO是一个同步的阻塞的IO模型,引出我们NIO,NIO是non-blocking IOjava也称为new IO,是同步非阻塞的IO,下面我们直接上流程图吧

服务端会产生一个ServerSocketChannel,它会注册到selector中,用于服务端和客户端通信使用
client:客户端
客户端会产生一个SocketChannel,它会注册到seletor中,用户服务端和客户端通信使用
buffer:缓冲区
用于客户端和服务端进行数据传输使用,既可以read也可以wirte
channel:通道
包括服务端的ServerSocketChannel和客户端的SocketChannel的通道,它是连接客户端和服务端的通道,是一个双向的既可以读也可以写,都是通过buffer完成的
selector:多路复用器
负责管理channel
selectedKeys:用于获取SocketChannel
2.1 服务端代码
//同步非阻塞 把整个过程分为三部分 建立连接 接收客户端数据(在客户端写之前,不会触发本事件,可以去处理其它的事件) 向客户端发送数据 类似于生产者消费者
public class NioServer { public static void main(String[] args) throws IOException {
// 打开服务端通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 设置成非阻塞
ssc.configureBlocking(false);
// 绑定端口
ssc.socket().bind(new InetSocketAddress(9000));
// 打开多路复用器
Selector selector = Selector.open();
// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接感兴趣
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
System.out.println("等待事件发生。。");
// 选择多路复用器里面的通道,方法是阻塞的,只有存在客户端进行连接,才可以执行后面逻辑
int select = selector.select();
System.out.println("有事件发生了。。");
// 获取多路复用器里面的所有注册的通道key
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
// 获取某一个通道key
SelectionKey key = it.next();
// 删除当前可以,防止多次处理
it.remove();
handle(key);
}
}
} private static void handle(SelectionKey key) throws IOException {
// 验证当前通道属于什么事件
if (key.isAcceptable()) { // 连接事件
System.out.println("有客户端连接事件发生了。。");
// 获取当前key所在的通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 调用accept()方法获取客户端SocketChannel通道。
//注意这里是阻塞状态的,但是也是非阻塞状态的,这里就是NIO的精髓
//我给大家讲解一下,accept()方法本身是阻塞,它要有客户端连接进来才能向下执行
//前面已经判断是Acceptable()事件,所以一定有客户端进行连接,所以这里就不用等待了
SocketChannel sc = ssc.accept();
// 设置通道为非阻塞方式
sc.configureBlocking(false);
// 将通道注册到多路复用器上,并且注册事件是OP_READ时间
sc.register(key.selector(), SelectionKey.OP_READ);
} else if (key.isReadable()) {
System.out.println("有客户端数据可读事件发生了。。");
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = sc.read(buffer);
if (len != -1){
System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
}
ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
sc.write(bufferToWrite);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
SocketChannel sc = (SocketChannel) key.channel();
System.out.println("write事件");
key.interestOps(SelectionKey.OP_READ);
}
} }
2.2 客户端代码
//把整个过程分为三个事件 建立连接 向服务端发送数据 接收服务端数据(在服务端写之前,不会触发本事件,线程就可以去处理其它的事件)
public class NioClient { private Selector selector; public static void main(String[] args) throws IOException { new Thread(()->{
try {
co(Thread.currentThread().getName());
} catch (IOException e) {
e.printStackTrace();
}
}, "客户端1" ).start(); } public static void co(String clientname ) throws IOException {
NioClient nioClientDemo = new NioClient();
// 初始化客户端
nioClientDemo.initClient("localhost",9000);
// 客户端进行对应操作
nioClientDemo.connect( clientname);
} /**
* 初始化客户端
* @param ip
* @param port
* @throws IOException
*/
private void initClient(String ip,int port) throws IOException {
// 获取socket通道
SocketChannel socketChannel = SocketChannel.open();
// 配置非阻塞
socketChannel.configureBlocking(false);
// 打开多路复用器
this.selector = Selector.open();
// 连接服务端 其实改方法并没有实现连接,
// 需要在listen()方法中调用channel.finishConnect();才能完成连接
socketChannel.connect(new InetSocketAddress(ip,port));
// 将socket注册到多路复用器,事件为SelectionKey.OP_CONNECT事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
private void connect(String clientname) throws IOException { while (true){
// 监听多路复用器里面是否存在需要处理的channel 这里是阻塞的
selector.select();
// 获取多路复用器中的channel对应的key
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
// 获取SelectionKey
SelectionKey key = iterator.next();
// 获取到之后把当前可以删除,防止重复获取
iterator.remove();
// 验证SelectionKey对应的事件
if(key.isConnectable()){
// 获取通道
SocketChannel channel = (SocketChannel) key.channel();
if(channel.isConnectionPending()){
channel.finishConnect();
}
// 配置成非阻塞方式
channel.configureBlocking(false);
// 写入缓冲流
String msg = clientname + ":Hello server";
ByteBuffer wrap = ByteBuffer.wrap(msg.getBytes());
// 向服务端写入信息
channel.write(wrap);
// 把当前通道注册到多路复用器中,并且注册事件是OP_READ事件
channel.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
read(key);
}else if(key.isWritable()){
System.out.println("客户端开始写事件");
} }
}
}
/**
* 进行读消息
* @param key
* @throws IOException
*/
private void read(SelectionKey key) throws IOException {
// 获取通道
SocketChannel channel = (SocketChannel) key.channel();
// 设置缓存流一次读取的大小
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取消息
int len = channel.read(buffer);//channel.read()不是阻塞的
if(len != -1){
System.out.println("客户端收到消息:" + new String(buffer.array(),0,len)) ;
}
} }
2.3 执行
为了方便测试,上面客户端代码再复制一份,改一下客户端名称,现在就有两个客户端了
在服务端ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());打个断点
在两个客户端的ByteBuffer wrap = ByteBuffer.wrap(msg.getBytes());打个断点



启动服务端

启动客户端1
客户端1:
在断点处停住了
服务端:
注意,此时,服务端在int select = selector.select();处阻塞,等待事件的到来
我们可以启动客户端2来看效果

启动客户端2:
客户端2:
停在断点处
服务端:
发现,服务端处理了客户端2的连接事件

放开客户端1的断点
客户端1:写完后,客户端在selector.select();处阻塞,等待事件的到来
服务端:读取到客户端1的数据,并且在断点处停住了

放开服务端断点
服务端:发送完数据。再次等待下一个事件的到来

客户端1:接收完数据,等待下一个事件(一般来说到这里请求就处理完成了)

放开客户端2的断点:
客户端2:写完后,客户端在selector.select();处阻塞,等待事件的到来
服务端:读取到客户端2的数据,并且在断点处停住了

放开服务端断点
服务端:发送完数据。再次等待下一个事件的到来
服务端:

客户端2:接收完数据,等待下一个事件(一般来说到这里请求就处理完成了)

它的关键是把建立连接,发送数据,接收数据分为三个事件来处理。
这样建立完连接,服务器就空闲下来。等待处理下一个事件。
就算这个请求的客户端在write之前去处理业务需要花费很多时间,也不会阻塞服务端。服务端可以去处理其它请求的事件。所以是非阻塞。
BIO和NIO的基本用法和API讲解的更多相关文章
- Java BIO、NIO、AIO 学习(转)
转自 http://stevex.blog.51cto.com/4300375/1284437 先来个例子理解一下概念,以银行取款为例: 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Ja ...
- Java BIO、NIO、AIO-------转载
先来个例子理解一下概念,以银行取款为例: 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写). 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Ja ...
- BIO与NIO、AIO的区别
IO的方式通常分为几种,同步阻塞的BIO.同步非阻塞的NIO.异步非阻塞的AIO. 一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个Serve ...
- Tomcat在Linux服务器上的BIO、NIO、APR模式设置
一.BIO.NIO.AIO 先了解四个概念: 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写). 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时, ...
- tomcat并发优化之三种接收处理请求方式(BIO、NIO、APR)介绍
原文链接:http://blog.csdn.net/xyang81/article/details/51502766 Tomcat支持三种接收请求的处理方式:BIO.NIO.APR 1>.BIO ...
- 操作系统层面聊聊BIO,NIO和AIO (epoll)
BIO 有了Block的定义,就可以讨论BIO和NIO了.BIO是Blocking IO的意思.在类似于网络中进行read, write, connect一类的系统调用时会被卡住. 举个例子,当用re ...
- Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)
本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解. 下面代码中会使用这样一个例子:客户端发送一段算式的字符串到服务器,服务器计算后返回结果到客户端. 代码的所有说明,都直接作为 ...
- [转帖] BIO与NIO、AIO的区别
培训里面讲的东西 自己查了下 啥意思,,, 转帖强化一下. http://blog.csdn.net/skiof007/article/details/52873421 IO的方式通常分为几种,同步 ...
- JDK中关于BIO,NIO,AIO,同步,异步介绍
在理解什么是BIO,NIO,AIO之前,我们首先需要了解什么是同步,异步,阻塞,非阻塞.假如我们现在要去银行取钱: 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写) ...
- BIO与NIO、AIO的区别(这个容易理解)
转自:http://blog.csdn.net/skiof007/article/details/52873421 BIO与NIO.AIO的区别(这个容易理解) IO的方式通常分为几种,同步阻塞的BI ...
随机推荐
- TensorFlow深度学习!构建神经网络预测股票价格!⛵
作者:韩信子@ShowMeAI 深度学习实战系列:https://www.showmeai.tech/tutorials/42 TensorFlow 实战系列:https://www.showmeai ...
- 如何在JavaScript中使用for循环
前言 循环允许我们通过循环数组或对象中的项并做一些事情,比如说打印它们,修改它们,或执行其他类型的任务或动作.JavaScript有各种各样的循环,for循环允许我们对一个集合(如数组)进行迭代. 在 ...
- Kubernetes核心技术Pod
Kubernetes核心技术Pod Pod概述 Pod是K8S系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型,也是在K8S上运行容器化应用的资源对象,其它的资源对象 ...
- 使用CRD扩展Kubernetes API
本文是如何创建 CRD 来扩展 Kubernetes API 的教程.CRD 是用来扩展 Kubernetes 最常用的方式,在 Service Mesh 和 Operator 中也被大量使用.因此读 ...
- UBOOT编译--- UBOOT全部目标的编译过程详解(九)
1. 前言 UBOOT版本:uboot2018.03,开发板myimx8mmek240. 2. 概述 本文接续上篇文章,采用自下而上的方法,先从最原始的依赖开始,一步一步,执行命令生成目标.这里先把上 ...
- requests模块/openpyxl模块/简单爬虫实战
内容概要 第三方模块的下载及使用 网络爬虫及requests模块 网络爬虫实战爬取二手房信息 自动化办公领域模块openpyxl 练习题及答案 第三方模块的下载 第三方模块就类似与别人写好的模块,我们 ...
- 使用 Bytebase 管理 Rainbond 上的应用数据库
在应用的发布过程中数据库的结构变更一直是最复杂也是风险最大的环节,而 Bytebase 可以对这一过程进行全生命周期的管理.在 Rainbond 中安装 Bytebase,轻松管理部署在 Rainbo ...
- Visual Studio高版本 ArcObject for .Net 低版本
在基于ArcGIS的开发中,经常会存在Visual Studio版本高,ArcObject for .Net 版本低的问题.例如Visual Studio 2015的环境下,安装ArcObject f ...
- React Server Component: 混合式渲染
作者:谢奇璇 React 官方对 Server Comopnent 是这样介绍的: zero-bundle-size React Server Components. 这是一种实验性探索,但相信该探索 ...
- 【kafka】connect的timestamp模式无法同一秒插入多条记录问题解决
一.现在问题 同时插入多条时间戳相同的记录 INSERT INTO "ABANK" VALUES ('1', 'CH', '00211', 'UBS Switzerland AG' ...