一、NIO简介
1)Java BIO : 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
2)Java NIO : 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。
二、操作系统的几个概念
1、内核态和用户态
内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,CUP也可以将自己从一个程序切换到另一个程序。
用户态:只能受限的访问内存,且不允许访问外围设备,占用CPU的能力被剥夺。
为什么要有用户态和内核态?
因为需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 – 用户态和内核态。
什么时候会发生内核态和用户态的切换?
【用户态在需要申请外部资源的时候会切换至内核态】。比如执行系统调用、发生中断、异常等,内核态执行完成会回退至用户态。
2、系统中断
中断的分类:
【中断源】是指能够引起中断的原因。一台【处理器】可能有很多中断源,但按其性质和处理方法,大致可分为如下五类:
- 机器故障中断,比如掉电。
- 程序性中断。现行程序本身的异常事件引起的,可分为以下三种:一是程序性错误,非法操作和除数为零等;二是产生特殊的运算结果,例如定点溢出;三是程序出现某些预先确定要跟踪的事件,跟踪操作主要用于程序调试。有些机器把程序性中断称为“异常”,不称为中断。
- IO-【输出设备】中断,IO中断。
- 外中断。来自控制台【中断开关】、计时器、时钟或其他设备,这类中断的处理较简单,实时性强。
- 调用管理程序。用户程序利用专用指令“调用管理程序”发【中断请求】,是用户程序和操作系统之间的联系桥梁。
系统中断有什么好处:
1、分时操作,解决CPU的快速处理和慢速IO设备的问题。
2、实时处理,word中可以一边打字一边做拼写检查。
3、故障处理,会优先处理故障。
3.DMA
DMA(Direct Memory Access,直接存储器访问) ,它允许不同速度的硬件装置来沟通,而不需要依赖于[ CPU ](https://baike.baidu.com/item/ CPU /120556)的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
当CPU需要访问外设(磁盘、网卡、usb)的数据时,将任务丢给DMA,有DMA负责利用总线将数据先拷贝到内存,DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。传输结束后,发出中断信号,通知CPU。
简而言之就是解决不同设备运行速度带来的问题,CPU运行速度快等待其他设备就会浪费CPU资源。
4、数据结构位图bitmap:
有一个场景:需要你统计你的同事的一个月的打卡记录。
你要怎么做,创建三十几个变量,0代表没打卡,1代表已打卡?
事实上我们使用一个int能表示:
11111111 10101111 11111111 11111110
一个int四个字节,就是三十二位,从第0位开始算第一天的打卡记录,那么有三十二位足够了,因为一个月最多也就31天。
我们能很简单的看出他第10天和12天没有打卡。
三、NIO相关的系统调用
首先,每个客户端连接在Linux系统下,都有一个文件描述符fd与之对应,文件描述符有一个编号,不同的编号表示不同的连接。
文件描述符
比如我们执行系统调用,常见文件,打开文件等。
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
flags:
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以读和写的方式打开文件
上面三个只能选择一个,下面的可以合理的任意组合:
O_CREAT 打开文件,如果文件不存在则建立文件
O_APPEND 强制write()从文件尾开始
mode:参数可选:
#define S_IRWXU 00700 文件所有者可读可写可执行
#define S_IRUSR 00400 文件所有者可读
#define S_IWUSR 00200 文件所有者可写
#define S_IXUSR 00100 文件所有者可执行
#define S_IRWXG 00070 文件用户组可写可读可执行
#define S_IRGRP 00040 文件用户组可读
#define S_IWGRP 00020 文件用户组可写
#define S_IXGRP 00010 文件用户组可执行
#define S_IRWXO 00007 其他用户可写可读可执行
#define S_IROTH 00004 其他用户可读
#define S_IWOTH 00002 其他用户可写
我们发现这两个系统调用(函数)有一个int类型的返回值,这个返回值就是文件描述符。
如同:
File file = new File("C://a.txt");
中的file。
1、select系统调用
select系统调用有一个重要参数,为fd文件描述符集合,即你要监听哪些文件描述符(哪些连接),这个文件描述符集合rset用一个bitmap位图表示,位图大小为1024,即最多只能监听1024个客户端连接。
当发起系统调用时,会将rset拷贝到内核态,然后内核态监听有没有数据可以处理,监听的所有文件描述符都没有数据的话会一直阻塞,直到有数据时,将有数据的fd索引置一,然后返回给用户态
Select缺点:
- 位图大小默认1024,有上限。
- 每次都需要创建一个文件描述符位图并拷贝到内核态。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
- nfds:要检测的文件描述符数量,最大文件描述符加1。(避免遍历范围外的bitmap)
- readfds:指定了被读监控的文件描述符集ÿ