Nio学习第一天,三大组件
BIO 朝NIO的演变过程
只能支持一次的socket服务端
此种情况下,客户端发送一次数据以后,服务器端就会停止
public class OnceSocketTcpServer {
static byte[] buffer = new byte[1024];
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8080))
Socket socket = serverSocket.accept();
int read = socket.getInputStream().read(buffer);
String result = new String(buffer);
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
}
支持客户端多次发送请求
现在可以支持多个请求,但是,由于单线程执行,某个方法执行时间太久会造成程序的阻塞
public class OnceSocketTcpServer {
static byte[] buffer = new byte[1024];
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8080));
while(true) {
Socket socket = serverSocket.accept();
int read = socket.getInputStream().read(buffer);
String result = new String(buffer);
System.out.println(result);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
现在继续改进,支持多线程, 即伪异步方式
在伪异步的情况下,开启多线程的方式来处理。但是,并发的连接数过大,开启的线程过多。cpu资源占用太多
public class OnceSocketTcpServer {
static byte[] buffer = new byte[1024];
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8080));
while(true) {
final Socket socket = serverSocket.accept();
int read = socket.getInputStream().read(buffer);
new Thread(new Runnable() {
public void run() {
String result = new String(buffer);
System.out.println(result);
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
由于线程过多,我们采用线程池来处理
即时采用线程池的情况下,频繁的切换线程也会造成cpu资源的消耗和浪费
public class OnceSocketTcpServer {
static byte[] buffer = new byte[1024];
public static void main(String[] args) {
try {
ExecutorService executorService = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8080));
while(true) {
final Socket socket = serverSocket.accept();
int read = socket.getInputStream().read(buffer);
executorService.execute(new Runnable() {
public void run() {
String result = new String(buffer);
System.out.println(result);
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
什么是NIO
三个概念:
- channel管道
- Selector选择器
- Buffer缓冲区
这里先抛砖引玉,为什么redis是单线程,却能够支持高并发
我们需要提到一个概念,多路IO复用机制,这是什么意思呢?
多路:实际上指的就是多个Tcp连接,即上述的多个管道
IO复用:将多个Tcp连接(管道)统一交给一个Selector选择器进行管理。最后,统一使用buffer将数据写入硬盘。
buffer的作用是什么呢
每次单独写入磁盘,效率会特别底下,所以,将数据汇集的多一些以后一起写入,可以提高效率
Bio和Nio到底有什么区别
Bio
在数据没有内核空间的时候,程序会一直阻塞,这个时候cpu放弃了使用权,不能干其他事情
Nio
不管有没有拿到数据都会立即返回结果,如果返回的结果没有数据,会循环请求数据,如果拿到了数据,程序继续执行。这种情况下程序并不会阻塞
Nio、Selector、Channel、Buffer原理
- Nio: 因为上面已经讲述了,这里就不在赘述了
- Selector: Selector选择器,也可以叫做多路复用器,在单线程的情况下维护多个不同的channel
- Channel:客服端传输的数据都必须经过管道,统一注册到selector中管理
- Buffer:BIO是按照字节来写入,效率低下。 缓冲区,将数据添加到缓冲区中,一次性写入,效率更高
Nio架构流程图
Nio的实现步骤
此种实现方式还有一些缺陷,如果客户端断开连接,需要将该连接从Selector选择器中移除。
当然,此处实现还是颇有问题,当某些客户端已经连接上,即存在于Selector中,但是,一直没有发送消息,如果这样的连接过多,也会造成大量的资源浪费。
public class OnceSocketTcpServer {
static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
static List<SocketChannel> selectors = new ArrayList<>();
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
// 设置为异步接收
serverSocketChannel.configureBlocking(false);
while(true) {
// 接收管道内的数据
SocketChannel socketChannel = serverSocketChannel.accept();
// 如果数据不为空,则讲管道添加到selector选择器中
if (socketChannel != null) {
socketChannel.configureBlocking(false);
selectors.add(socketChannel);
}
// 遍历选择器中的所有管道,看是否已经发送数据
for (SocketChannel sel : selectors) {
int j = sel.read(byteBuffer);
if (j > 0) {
byteBuffer.flip();
byte[] bytes = Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
System.out.println("已经获取到了数据" + new String(bytes));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO复用机制、信号驱动IO、异步IO的区别
- IO复用机制:将多个Tcp连接加载到管道中,然后,交由选择器管理,接收到消息以后统一写入硬盘
- 信号驱动IO:采用事件驱动的方式,有回调来通知
- 异步IO,即AIO