BIO
- BIO:blocking io,阻塞等待
- 例如:创建一个server后,将等待client的连接到来,程序不会向下执行。当连接建立后,触发server去读取client信息。在read client的发送的信息的时候,同样是阻塞的。未拿到client的信息,依然不会进行继续执行。
- 缺点:无法处理多个client的连接
-> 对read client data新开一个线程,使得其可以连接多个client,但开销过大,CPU无法支撑。
-> 线程池:存在上限,若client数据传输过慢等情况,client将长时间占用线程,使得后续client无法获得线程,进行数据传输。同时,被占用的线程被浪费了。
NIO
- NIO:non-blocking io, 不会阻塞,哪怕没有连接,没有数据传输
多路复用器 selector
- 案例:
- 基本思路:常见场景,server端与多个client连接,但只有少部分client在进行数据传输工作。若每次都遍历所有连接,进行数据接受,则会出现s端响应慢,资源浪费等问题。所以,采用集合分别存储 建立的连接 和 数据传输的连接。只对有数据传输的连接进行read数据。
- 方法:通过selector实现。 —— 等待事件发生,若没有事件发生,就会阻塞(底层不会占用资源)
对监听的接口,注册一个selector,则当有连接事件到来的时候,会进行处理,并得到一个socketChannel(服务端的)。
对连接后建立的socketChannel也进行selector注册,并标记注册read事件。则当该channel有read I/O时,会触发该事件,并进行处理。
ServerSocketChannel serverSocketChannel = ServerSocketChannel .open();
serverSocketChannel.socket.bind(new InetSocketAddress(9000);
// set to non blocking
serverSocket.configureBlocking(false);
//打开selector,即创建epoll
Selector selector = Selector.open();
//注册到selector上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
//等待事件发生
selector.select();
//获取selector中注册的全部事件实例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iteratior();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
if(key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if {key.isReadable()) {
SocketChannel socketChannel = (SocketChannel)key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(6);
//read data from channel
int len = socketChannel.read(byreBuffer);
//如果数据存在,则进行处理
if(len > 0) {
System.out.println("接受数据: " + new String(byteBuffer.array());
} else if(len == -1){//客户端断开连接
socketChannel.close();
}
}
//从事件集合中删除本次处理的key,防止下次重复操作
iterator.remove();
}
}
selector 底层原理
- selector实际上是通过epoll实现的,在java中,通过使用Selector.open 可以创建一个epoll实例,步骤如下:
- 调用Selector.open()方法 : 返回SelecotorProvider.provider().openSelector();
- provider()方法:返回createProvider(“sun.nio.ch.EpollSelectorProvider”);
- openSelector()方法:实际上是EpollSelectorProvider对象调用openSelector,该方法将会创建并返回一个EpollSelectorImpl对象
- EpollSelectorImpl实现:构建(初始化)时,初始化EpollArrayWrapper,注册的channel放置到该wrapper对象中。
- EpollArrayWrapper初始化:其将会调用epollCreate方法,epollCreate()是native方法。
- epollCreate方法:调用epoll_create方法,epoll_create是linux的系统函数,用于创建epoll对象。
- register方法则是将 注册的channel 放入c++层的epoll实例中
- selector.select则会调用到epoll_ctl epoll_wait等系统函数
epoll
#include <sys / epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event * event);
该系统调用对文件描述符epfd引用的epoll实例执行控制操作。它要求操作op对目标文件描述符fd执行。
op参数的有效值为:
EPOLL_CTL_ADD:在文件描述符epfd所引用的epoll实例上注册目标文件描述符fd,并将事件事件与内部文件链接到fd。
EPOLL_CTL_MOD:更改与目标文件描述符fd相关联的事件事件。
EPOLL_CTL_DEL:从epfd引用的epoll实例中删除(注销)目标文件描述符fd。该事件将被忽略,并且可以为NULL(但请参见下面的错误)。
(即op对目标fd进行操作:添加,修改,删除)
epoll_wait,等待epoll文件描述符上的I / O事件。若有事件,则取出,进行响应。若无,则阻塞等待。
#include <sys / epoll.h>
int epoll_wait(int epfd,struct epoll_event * events, int maxevents,int timeout);
- events 指针指向的内存包含对调用者有效的事件。当达到 maxevents 时 epoll_wait() 会返回。maxevents 参数必须大于零
- 调用等待最长时间是 timeout 毫秒。指针 timeout 为 -1 让 epoll_wait() 无限期地等待,若指定 timeout 为零时即使在没有事件有效 epoll_wait() 也会立即返回(返回值是零)。
从epoll_ctl到epoll_wait列表中,是通过操作系统的中断程序实现的。也即是,利用epoll_ctl注册监控的通路以及关注事件。当这些事件发生时,操作系统的中断程序将把相应的数据和信息放到,epoll_wait中的队列中。再由epoll_wait的队列取出,被上层感知和处理。
select, poll and epoll 三者区别
- selector最开始的底层实现是基于 select的,后来演进出了poll,epoll等。
- select 和poll,都是对所以注册的channel进行遍历,看其是否有事件发生。而epooll则是通过事件通知的机制 实现对事件的监控。
radis线程模型
- radis是基于epoll实现的
- 利用epoll_create创建epoll,使用epoll_ctl注册监控同类和关注事件,通过操作系统中断程序完成事件放置到epoll_wait队列中,通过epoll_wait队列获取到事件和相应信息,完成多路复用。
- 多路复用通俗理解:多条通路,一条线程进行管理。