Linux通信总结——阻塞/非阻塞、同步/异步、IO模型
1.阻塞/非阻塞,同步/异步
-
一次典型的IO包括两个阶段:数据就绪和数据读写
-
数据就绪阶段:阻塞和非阻塞
-
阻塞:数据还没到达之前,调用IO方法的线程会挂起(并不占用CPU资源)
-
非阻塞:不会改变线程的状态,会通过返回值判断缓冲区是否有数据传来 int size = recv(sockfd, buf, 1024, 0);
-
size = -1,读取出错(但是要特殊判断下列3种情况:EINTR,EAGAIN,EWOULDBLOCK)
-
size = 0,读取到数据末尾,或者对方连接关闭
-
size > 0,读取到多少数据
-
在处理IO的时候,阻塞和非阻塞都是同步IO,只有使用了特殊API的才是异步IO。
-
数据读写阶段:同步和异步
-
同步:A向B请求调用网络IO接口时,数据的读写都是由请求方A自己完成
-
异步:...,A向B传入请求的事件以及事件发生时通知的方式,A就可以处理其他业务逻辑了,当B监听到事件处理完成之后,会用事先约定好的通知方式,通知A处理结果。
-
Linux异步IO接口 aio_read(),aio_write()
-
同步的意思就是当调用一个I/O syscall后,直到有确定结果返回后,应用程序都是被阻塞的内核态,时间片被换出,完全卡住不能运行。异步就是调用完syscall后,不存在任何阻塞内核态,应用程序继续执行直到时间片用完。
-
全异步控制非常困难并且要求很高,现在常用的是同步非阻塞,这里面也是使用同步调用read/write,但是fd是非阻塞的,这时候调用这两个syscall只会短暂的切换到内核态如果没有数据会立刻返回给应用EAGAIN,这样应用可以选择等一会再来一次,或者等待EPOLL的事件之后再来一次。这种方式被广泛用于现在主流的各种服务器的网络socket操作,反向代理等等软件上比如nginx。为什么只有socket操作用会比较好呢,因为网络操作底层kernel都是在处理buffer,小规模的操作并不会导致太大的延迟,所以返回都比较快,不会造成很大延迟。
-
需要注意这时候调用read的时候也会产生上下文切换,这时候严格说也是阻塞的,但是阻塞时间非常短。
-
全异步的aio_read不存在这个短暂时间,效率更高,但是也更难控制,因为需要自己处理流程和buffer的问题,非常难搞。这个一般用于即使较小的规模的I/O操作也可能造成很大延时的操作,典型的就是磁盘读写。例子就是nginx的读文件的操作。
2.IO模型
-
阻塞IO
-
调用者调用了某个函数,等待这个函数返回,期间什么都不做,不停检查这个函数有没有返回,必须等该函数返回才能进行下一步动作。
(阻塞IO)
- 非阻塞IO
- 每隔一段时间去检测IO时间是否就绪,没有就绪就可以进行其他操作。
- 非阻塞IO执行系统调用总是立即返回,不管事件是否已经发生,若没发生则返回-1,此时根据errno区分两种情况,对于accept、recv和send,事件未发生时,errno被设置为EAGAIN。
(非阻塞IO)
-
IO复用
-
select、poll、epoll函数实现,这些函数也会使进程阻塞,但是和阻塞IO不同的是,这些函数可以同时阻塞多个IO操作。
-
可以同时对多个读操作、写操作的IO函数进行检测,直到有数据可读或可写时,才真正调用IO操作函数。
(IO复用)
- 信号驱动IO
- 注册新号处理函数,进程继续运行并不阻塞,当IO事件就绪时,进程受到SIGIO新号,然后处理IO事件。
- 内核在第一个阶段是异步,在第二个阶段是同步。
- 与非阻塞IO的区别在于它提供了消息通知机制,不需要用户进程不断的轮询检查,减少了系统API的调用次数,提高了效率。
(信号驱动IO)
-
异步IO
-
LInux中,调用aio_read函数告诉内核描述字缓冲区指针和缓冲区大小 、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。
(异步IO)