IO 就是指“输入和输出”,由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等就需要IO接口。根据针对的对象不同,IO模式可以分为磁盘IO模型和网络IO模型。
IO操作会涉及到用户空间和内核空间的转换,先来理解以下规则:
原文链接:https://blog.csdn.net/m0_62262008/article/details/126616938
在物理机上磁盘、网卡、键盘、鼠标其实都是IO设备,我们常说的IO基本上分了两类:磁盘IO、网络IO
磁盘IO
也就是我们常写的File、FileInputStream…Api,这些Api会和内核产生系统调用,读写磁盘数据,涉及知识:VFS、FD 、pagecache(4k),dirty 。
网络IO
我们常说的TCP、socket,三次握手等都是网络IO会涉及到的知识,这些东西说来很多,要注意的很多,所以我在这里只整理了网络模型的NIO、Bio的一些知识。
图解 推荐
文件描述符 FDfd,即file descriptor[文件描述符]。linux下,所有的操作都是对文件进行操作,而对文件的操作是利用[文件描述符]来实现的。
每个文件[进程控制块]中都有一份文件描述符表(可以把它看成是一个数组,里面的元素是指向file结构体指针类型),这个数组的下标就是文件描述符。在源代码中,一般用fd作为[文件描述符]的标识
内核态(Kernel Mode):运行操作系统程序,操作硬件
用户态(User Mode):运行用户程序
**用户态—>内核态:**唯一途径是通过中断、异常、陷入机制(访管指令)
**内核态—>用户态:**设置程序状态字PSW
TCP协议Tcp 是一种面向连接的、可靠的、基于字节流的传输通信协议
建立起一个TCP连接需要经过“三次握手”:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
连接:三次握手结束,开辟资源,可以为对方提供了服务
SOCKET(套接字)编程BerkeleySocket最初是1983年发布的4.2BSD(BerkeleySoftwareDistribution)UNIX系统中为TCP/IP协议实现的应用编程接口。随着UNIX的发展,socket被广泛应用于各种网络软件中。目前,它已成为网络编程接口的事实标准。套接字位于应用程序和协议栈之间,包含一组函数。应用程序通过调用套接字函数来使用TCP/IP提供的网络服务。
更像是一种对于TCP,UDP的封装的编程接口,包含了五组信息:协议,客户端ip,客户端端口,服务端ip,服务端端口,四元组对应一个连接
socket 其实就是操作系统提供给程序员操作「网络协议栈」的接口,说人话就是,你能通过socket 的接口,来控制协议找工作,从而实现网络通信,达到跨主机通信。
Socket 一套规范的 Api
无论在哪种编程语言在OS上 【使用的都是大同小异的】
IO模型主要分类同步(synchronous) IO和异步(asynchronous) IO
阻塞(blocking) IO和⾮阻塞(non-blocking)IO
同步阻塞(blocking-IO)简称BIO
同步⾮阻塞(non-blocking-IO)简称NIO
异步⾮阻塞(synchronous-non-blocking-IO)简称AIO
同步:自己RW
异步:内核完成RW 不访问IO buferr
1.BIO (同步阻塞I/O模式)
数据的读取写⼊必须阻塞在⼀个线程内等待其完成。
这⾥使⽤那个经典的烧开⽔例⼦,这⾥假设⼀个烧开⽔的场景,有⼀排⽔壶在烧开⽔,BIO的⼯作模式
就是, 叫⼀个线程停留在⼀个⽔壶那,直到这个⽔壶烧开,才去处理下⼀个⽔壶。但是实际上线程在等
待⽔壶烧开的时间段什么都没有做。
2.NIO(同步⾮阻塞)
同时⽀持阻塞与⾮阻塞模式,但这⾥我们以其同步⾮阻塞I/O模式来说明,那么什么叫做同步⾮阻塞?如
果还拿烧开⽔来说,NIO的做法是叫⼀个线程不断的轮询每个⽔壶的状态,看看是否有⽔壶的状态发⽣
了改变,从⽽进⾏下⼀步的操作。
调用一瞬间返回结果,自己解决什么时候去读
3.AIO (异步⾮阻塞I/O模型)
异步⾮阻塞与同步⾮阻塞的区别在哪⾥?异步⾮阻塞⽆需⼀个线程去轮询所有IO操作的状态改变,在相
应的状态改变后,系统会通知对应的线程来处理。对应到烧开⽔中就是,为每个⽔壶上⾯装了⼀个开
关,⽔烧开之后,⽔壶会⾃动通知我⽔烧开了。
JAVA AIO框架在windows下使用windows IOCP技术,在Linux下使用epoll多路复用IO技术模拟异步IO,linux目前没有通过内核的异步处理方案
5.同步与异步的区别
同步:发送⼀个请求,等待返回,再发送下⼀个请求,同步可以避免出现死锁,脏读的发⽣。
异步:发送⼀个请求,不等待返回,随时可以再发送下⼀个请求,可以提⾼效率,保证并发。
同步:自己RW
异步:内核完成RW 不访问IO buferr
6.阻塞和⾮阻塞
阻塞:传统的IO流都是阻塞式的。也就是说,当⼀个线程调⽤read()或者write()⽅法时,该线程将被阻
塞,直到有⼀些数据读读取或者被写⼊,在此期间,该线程不能执⾏其他任何任务。在完成⽹络通信进
⾏IO操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供⼀个独⽴的线程进⾏处理,当服
务器端需要处理⼤量的客户端时,性能急剧下降。
⾮阻塞:Java
NIO是⾮阻塞式的。当线程从某通道进⾏读写数据时,若没有数据可⽤时,该线程会去执⾏其他任务。
线程通常将⾮阻塞IO的空闲时间⽤于在其他通道上执⾏IO操作,所以单独的线程可以管理多个输⼊和输
出通道。因此NIO可以让服务器端使⽤⼀个或有限⼏个线程来同时处理连接到服务器端的所有客户端。
7.BIO、NIO、AIO适⽤场景
public class BioServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9090);
while (true) {
System.out.println("等待连接");
Socket client = serverSocket.accept();
new Thread(() ->{
try {
handler(client);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
public static void handler(Socket socket) throws IOException {
byte[] bytes = new byte[1024];
System.out.println("准备read");
int read = socket.getInputStream().read(bytes);
if (read != -1) {
System.out.println("接收到" + socket.getPort() + "客户端的数据 :" + new String(bytes, 0, read));
}
}
}
Client
cmd 模拟
总结:
1、单线程,只能处理一个连接,新的连接过来了只能等待正在循环中的线程结束
2、一个连接就是一个线程,线程消耗资源,且用户态和内核态的切换,上下文的切换等都会造成性能消耗,连接多了,效率低了
经典问题:C10K C10M www.kegel.com
不想要那么多的accept read/wirte处理,我就想一个线程处理多个连接?
NIONon-blocking-io
BufferBuffer是⼀个对象。它包含⼀些要写⼊或者读出的数据。在⾯向流的I/O中,可以将数据写⼊或者将数据,直接读到Stream对象中。
在NIO中,所有的数据都是⽤缓冲区处理。IO是⾯向流的,NIO是⾯向缓冲区的。
缓冲区实质是⼀个数组,通常它是⼀个字节数组(ByteBuffer),也可以使⽤其他类的数组。但是⼀个缓冲区不仅仅是⼀个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。
最常⽤的缓冲区是ByteBuffer,⼀个ByteBuffer提供了⼀组功能于操作byte数组。除了ByteBuffer,还有其他的⼀些缓冲区,事实上,每⼀种Java基本类型(除了Boolean)都对应⼀种缓冲区,具体如下:
Channel是⼀个通道,可以通过它读取和写⼊数据,他就像⾃来⽔管⼀样,⽹络数据通过Channel读取和写⼊。
通道和流不同之处在于通道是双向的,流只是在⼀个⽅向移动,⽽且通道可以⽤于读,写或者同时⽤于读写。
因为Channel是全双⼯的,所以它⽐流更好地映射底层操作系统的API,特别是在UNIX⽹络编程中,底层操作系统的通道都是全双⼯的,同时⽀持读和写。
Channel有四种实现:
jdk1.4之后,多路复用器,操作系统的select poll epoll
多路复⽤器Selector
Selector选择器可以监听多个Channel通道感兴趣的事情(read、write、accept(服务端接收)、connect,实现⼀个线程管理多个Channel,节省线程切换上下⽂的资源消耗。Selector只能管理⾮阻塞的通道,FileChannel是阻塞的,⽆法管理。
关键对象
监听注册
监听注册在Selector
socketChannel.register(selector, SelectionKey.OP_READ);
监听的事件有
源码追踪
1、epoll_create(size)
创建一个epoll实例,并返回一个非负数作为文件描述符,用于对epoll接口后续调用
*2、epoll_ctl(int epfd,int op,int fd,struct epoll_eventevent)
使用文件描述符epfd 医用的epoll实例,对目标文件描述符fd执行op操作
参数epfd标识epoll对应的描述符,参数fd标识socket对应的文件描述符
op: EPOLL_CTL_ADD:注册新的fd到epfd中,并关联事件event,Mod:修改已经注册的fd监听事件,DEL:epfd移除fd,忽略绑定的事件
3、epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
等待文件描述符上的事件
events 标识调用者所有可用事件,maxevents 表示等到多少个事件就返回,timeout是超时时间
I/O多路复用底层就是linux内核函数(select,poll,epoll)来实现,windows 不支持epoll,基于winsock2的select函数实现的
select | poll | epoll | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | 哈希表 |
IO效率 | 每次调用线性遍历 | 每次调用线性遍历 | 事件通知方式 |
无论NIO,select,poll都是要遍历所有IO,询问状态,不过NIO遍历的过程成本在用户内核态切换
select,poll 实在遍历的时候触发一次系统调用,用户态内核态切换,过程中把fds传递给内核,内核根据传过来的遍历修改状态
select,poll问题:每次都要重新重复传递fds,每次内核被调用后,针对这个调用 遍历fds全量的复杂度,有多少个遍历多少个,线性增加
epoll 怎么通知的?
epoll 感知---->中端程序
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行
过程:中断-内核回调-系统调用-io状态设置-io读取-读取后的处理
补充:
reactor
multiple Reactors
异步编程也是这些问题的一个解决方向
感谢B站图灵-诸葛老师的公开课、以及部分不知道那不知道从哪里搜索到的知识点的博客,感谢,如有侵权请联系我加外链!
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
售后响应及时
7×24小时客服热线数据备份
更安全、更高效、更稳定价格公道精准
项目经理精准报价不弄虚作假合作无风险
重合同讲信誉,无效全额退款