IO模型就是选用什么样的通道进行数据的接收和发送,JAVA支持的三种网络IO编程模式分别为:Bio、Nio、Aio。
Bio(blocking IO)
- 同步阻塞模型,一个客户端对应一个县城。如果服务端启动后没有客户端来连接,那么会一直阻塞。在客户端连接后,没有信息传输也会进行阻塞。
- 缺点:
- 1、IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源。
- 2、如果线程很多,会导致服务器线程太多,压力太大,比如C10K问题。
Nio(non blocking IO)
- 非同步阻塞队列。在创建服务器绑定端口的时候就可以自主的设置是否为阻塞。和bio最大的不同是可以讲serversocket注册到selector上,也就是加入了多路复用器。
Selector selector = Selector.open(); 24 // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
- 在将serversocket注册到selector上后,在循环中进行阻塞
// 阻塞等待需要处理的事件发生 selector.select()
- 在对多路复用器中的key进行循环,根据key的不同事件状态进行连接或者读取。
public class NioSelectorServer {
public static void main(String[] args) throws IOException, InterruptedException {
// 创建NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
// 设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
// 打开Selector处理Channel,即创建epoll
Selector selector = Selector.open();
// 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务启动成功");
while (true) {
// 阻塞等待需要处理的事件发生
selector.select();
// 获取selector中注册的全部事件的 SelectionKey 实例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍历SelectionKey对事件进行处理
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是OP_ACCEPT事件,则进行连接获取和事件注册
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
// 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接成功");
} else if (key.isReadable()) { // 如果是OP_READ事件,则进行读取和打印
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
// 如果有数据,把数据打印出来
if (len > 0) {
System.out.println("接收到消息:" + new String(byteBuffer.array()));
} else if (len == ‐1) { // 如果客户端断开连接,关闭Socket
System.out.println("客户端断开连接");
socketChannel.close();
}
}
//从事件集合里删除本次处理的key,防止下次select重复处理
iterator.remove();
}
}
}
}
! nio阻塞后,不会无限制的去循环,会在接收到事件时,才会去处理。每次只会响应有事件请求的客户端。
- Nio有三大核心组件
- NIO 有三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)
- 1、channel 类似于流,每个 channel 对应一个 buffer缓冲区,buffer 底层就是个数组
- 2、channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理
- 3、NIO 的 Buffer 和 channel 都是既可以读也可以写
- NIO 有三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)
- nio底层是怎么感知有事件发生的呢?
- 在早期(jdk1.4)nio的底层是一个轮询的方式去查看有没有客户端进行连接的事件,然后进行处理。这样的性能会很低,如果连接数过多,那么每次循环所带来的也是会水涨船高。这种方法是基于用linux的内核函数select()或poll()来实现。
- 在JDK1.5开始引入了epoll基于事件响应机制来优化NIO。也就是如果有事件发生,客户端主动通知多路复用器请求事件,只处理发生事件请求的客户端,不会在次去全部循环处理。如下图:
- nio 功能实现最核心的三个方法:
- Selector.open() //创建多路复用器
- socketChannel.register() //将channel注册到多路复用器上
- selector.select() //阻塞等待需要处理的事件发生
多路复用器(selector)底层?
Selector selector = Selector.open(); //创建多路复用器
- selector底层是对linux c语言系统创建的epoll对象的封装
- linux中一切皆文件,所以在open()之后会创建epoll,底层会返回一个epfd。
- 在socketChannel.register(selector, SelectionKey.OP_READ); 之后,epollwrapper会调用add方法,将channel的fd保存起来,在调用 selector.select()时,底层会调用epolctl(),在这个方法内会真的进行epoll和channel的绑定。
- 当有事件发生时,会将socketchannel的事件添加到epoll底层的一个事件就绪list内radlist
- selector.select(); 会调用一个epollwait方法 查看rdlist中是否存在 有等待执行的事件,如果有的话 会执行事件不在阻塞,如果没有事件那么会继续阻塞。
Aio
-
对nio很多方法的封装,但是使用不多,附上一张图对比较下。
-
底层linux内调用的链路图。