IO多路复用
听了小刘讲源码的小刘老师视频整理,侵删~
BIO
BIO是什么,有什么缺点?
accept和socket读写都会阻塞线程,所以需要开多线程进行C/S交互,这样很难做到C10K。
BIO的B是blocking(阻塞),服务端开发,使用SeverSocket()绑定完端口,使用listen监听,等待accept(),accept会阻塞主线程。收到accept,程序就会收到一个客户端与当前服务端链接的socket,可针对该socket中进行读写。但是socket读写都会阻塞当前的线程,可以采用多线程的方法进行C/S交互,但这样很难做到C10K。
要1W个客户端链接服务端,那么1W个线程也难以支持,包括上下文切换,没办法执行。
NIO
NIO如何解决多客户端问题?
NIO包给我们提供一套非阻塞的接口,这样我们就不需要为每一个C/S长连接保留单独的处理线程,阻塞IO之所以要给每一个长连接保留一个线程,是因为它阻塞。NIO API具备非阻塞特性,就可以用一个线程去检测n个socket。NIO包提供了一个选择器selector,把需要检查的socket注册到selector中,主线程阻塞在selector#select方法里,当选择器发现某个socket就绪了,就会唤醒主线程,就可以通过selector获得就绪状态的socket并进行相对应的处理。
多路复用
select函数实现原理?
每次调用kernel里的select函数,都会涉及到用户态和内核态的切换,还需要传递需要检查的socket集合,也就是需要检查的(文件描述符ID)fd——Linux系统下一切皆文件,fd就是文件系统中socket生成的对应文件描述符。select函数被调用后,首先按照fd集合去检查内存中的socket状态,复杂度O(N),如果有就绪状态的socket就直接返回,不会阻塞当前调用的线程,否则就说明没有就绪状态的socket,那么阻塞当前线程,直到有就绪状态的socket。
select函数监听socket数量限制?
默认最大1024,实际小于1024。因为fd_set的结构是bitmap位图结构,默认长度是1024。另外一点可能是处于性能的考虑,因为select在检查到就绪状态socket后做了两件事,一标记当前的socket就绪,第二会返回select函数,唤醒Java线程,在Java层面,收到一个int值,表示有几个socket处于就绪状态。接着又是一个O(N)检查fd—set集合中有哪几个是就绪的。涉及到用户态和内核态的来回切换。
socket会一直占用cpu轮询检查吗?
补充(select第一次未发现就绪状态的socket,过段时间又反馈了就绪socket的状态,socket会一直占用cpu轮询检查吗?)
操作系统调度
:单核CPU在同一时刻只能运行一个进程,操作系统最主要的功能就是系统调度,就是有N个进程,让N个进程在CPU上切换进行。未挂起的进程都在工作队列内,都有机会获得CPU的执行权,挂起的进程会从工作队列内移除,反应到Java层面就是线程阻塞了。Linux系统下的线程其实就是轻量级的进程。
操作系统中断
:键盘打字,按下一个键之后会发生电流信号,主板感应到之后,就会触发CPU中断,所谓中断就是让正在执行的进程保留进程上下文,避让出CPU给中断让道。中断就会拿到CPU执行权,执行响应的程序。
select函数第一遍轮询没有发现就绪状态的socket,就会把当前进程保留给需要检查的socket等待队列中。socket有三大核心,读缓存、写缓存和等待队列,select函数会把当前进程保留到每个需要检查的socket等待队列之后,会把当前进程从工作队列移出。移出其实就是挂起当前进程,select函数也不会再运行。下一个阶段,客户端发送数据到服务端,数据通过网线到网卡,网卡到DMA硬件的方式将数据直接写在内存中,整个过程CPU不参与。当数据传输完之后,就会触发网络数据传输完毕的中断程序,中断程序会顶掉CPU当前执行的进程。
根据内存中的数据包,分析出数据包是哪个socket的数据,TCP/IP协议保证传输时候有端口号,所以数据包是有端口号的。根据端口号找到socket实例,将数据导入读缓存里,导入完成后就检查socket等待队列是不是有等待者,如果有就把等待者移入工作队列中,中断执行到这里就完成了。进程又在一起进入工作队列,获取到CPU时间片。当前进程再执行select函数,发现有socket准备就绪,就会标记当前就绪文件描述符fd,select函数执行完毕,返回Java层面。就会涉及内核态和用户态的转换。之后轮询检查fd是否被标记,处理被标记的socket。
poll与select的区别?
二者最大的不一样就是传参。select使用的bitmap,表示需要检查的socket集合,而poll使用数组结构,数组没有1024这个限制,可以监听多个线程。
epoll的产生背景?
select和poll的缺陷:
1.每次调用都需要提供所有需要监听的socket文件描述符集合,线程死循环调用select&poll函数,涉及到用户空间到内核空间的数据拷贝过程,虽然监听的socket集合数据变化非常小,但是该函数就较耗费性能,每次调用都进行数据拷贝。
2.返回值都是int型,需要再次循环检查哪个socket是就绪状态的。