先讲了五种IO模型
阻塞式IO:
readcv系统调用->阻塞->等待系统调用返回
非阻塞式IO
系统调用->无数据->不阻塞,直接返回->继续调用直到返回成功
这样很浪费CPU
IO复用模型
调用select->阻塞->等某个数据准备好->再调用readcv系统调用
信号驱动IO模型
调用Signal,等捕捉某个信号时,才调用recv系统调用
异步IO模型
发起IO,让内核完成读取,读取结束后告诉我们他自己帮我们读完啦,我们可以直接使用了。
比较:
前四种都是同步IO, 只有异步IO是异步IO
同步IO中,进程都要调用recv并阻塞, 请求进程会被阻塞直到完成
异步中,不会阻塞进程,工作由内核完成。
接上一章节的程序
当时那个回射程序存在一个问题: 如果服务器执行关闭, 此时客户进程并不会立即退出,因为它被卡在用户输入那里等待输入,实际上应该马上结束才对。
于是我们引入了select。
select可以帮我们绑定一些描述符(把描述符放入某集合), 然后阻塞。
当其中某些描述符有了 可读或可写或错误的状态 时,会立即返回。
该函数的参数为 select(要检查的描述符最大值, 读集合,写集合,错误集合, 超时时间)
返回的是已经就绪的描述符数量(即这次返回可能有好几个描述符都可以用了)
改进1:
根据select原理,我们把stdin描述符,和sock套接字描述符 与select绑定,然后进入select等待。
这时候只要有终端输入,或者收到服务器信息,都可以从select处返回。
返回后用FD_ISSET去判断是哪个描述符就绪了, 然后进行对应的操作,是Write,还是Puts
这样子的话,当服务器关闭时,收到的FIN会让select返回,然后readline读到FIN,报错,程序退出。
存在的问题: 当我们连续输入了很多个发送字符串,并且最终按下EOF时, 终端输入就结束了, 收到EOF后,进程会进行报错
,于是结束进程 ,但是!这时候可能路上还有数据在发回来,即还有剩余的数据要接收,不可以马上关闭!要等接收完再关闭。
改进2
当输入EOF后,不执行报错并退出, 而是用shutdown函数去发送FIN,通知服务器我发完啦
然后关闭并清除stdin描述符的绑定。并且设置一个flag
当套接字接收到的数据返回错误时,看一下flag,即判断一下终端输入是否结束了
如果结束了,说明这是正常的程序关闭,于是执行关闭。
如果输入还未结束却接收错误,说明出错了。
改进服务器程序, 用select来代替fork
这里的话,就是当监听进程监听到一个新连接,并从select返回时, 我们为该连接新建一个描述符,并绑定如select(这时候要修改一下描述符最大值)
该描述符通过一个数组去确定。
当select返回时,用for循环去判断是哪个描述符可读,并执行对应的读取操作,从缓冲区中获取信息。
select返回的是可就绪的描述符数量,通过这个数量去确定到底要处理几次。
pselect可以避免竞争,时间的精度也更加高,并且可以用指针去确定一个新参数
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。