Linux网络编程之socket编程(八)-select

这次我们将要学习五种I/O模型(阻塞I/O、非阻塞I/O、I/O复用、信号驱动I/O、异步I/O), select函数,以及用select改进回射客户端程序。

1.五种I/O模型

(1)阻塞I/O
阻塞I/O

1)前面用到的套接口编程都是用的阻塞I/O来进行编程的。
2)套接口完成连接请求之后,就可以向系统提交一个recv请求来接收数据,这个请求是阻塞的。

(2)非阻塞I/O非阻塞I/O

1)调用recv,并将套接口设置为非阻塞模式,将套接口设置为非阻塞模式,可以使用如下函数:
fcntl(fd, F_SETFL, flag|O_NONBLOCK);
2)这个时候,recv函数,即使没有数据到来,也不会阻塞。
3)该模型在数据没有到来时,需要循环接收。占用CPU时间片,该模型不推荐使用。

(3)I/O复用I/O复用

1)该模型主要通过select来实现的,思想是通过select来管理多个文件描述符。一旦检测到数据到来,select就返回,再调用recv函数就不会阻塞了。
2)然后将数据从内核空间拷贝到用户空间。一旦拷贝完成,就返回。

(4)信号驱动I/O信号驱动I/O

1)这种模型也不常用。

(5)异步I/O异步I/O

1)这种I/O的效率最高,通过aio_read函数实现。该函数提交一个请求并会递交了一个应用层缓冲区buf。即使内核中没有数据到来,该函数也立即返回。
2)当有数据到来,内核就会自动地将这些数据拷贝到应用层的缓冲区buf。一旦复制完成,会通过一个信号来通知应用进程的程序。

2.select
  • 原型:
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • 参数:
    • nfds:读、写、异常集合中的文件描述符的最大值+1
    • readfds:读集合
    • writefds:写集合
    • exceptfds:异常集合
    • timeout:超时结构体
  • 返回值:成功返回检测到的事件个数;失败返回-1

void FD_ZERO(fd_set *set);                /* clear all bits in set */
void FD_SET(int fd, fd_set *set);        /* turn on the bit for fd in set */
int FD_ISSET(int fd, fd_set *set);       /* is the bit for fd on in set? */
void FD_CLR(int fd, fd_set *set);        /* trun off the bit for fd in set */

(1)select可以管理多个I/O,一旦其中的一个或多个I/O检测到了我们感兴趣的事件,select就立刻返回,返回值为检测到的事件个数。将刚兴趣的时间填充到对应的集合当中。
(2)然后我们就可以轮询这些事件,一个个的来处理它们。
(3)这个时候的处理就不会阻塞了,因为select已经提前阻塞了。

3.select改进回射客户/服务端程序

(1)首先,我们先看一下客户端的通信模块代码:

其中,第5、8行分别涉及到标准输入I/O(stdin),和套接口I/O(sock)。

(2)现在我们使用select来统一管理标准输入I/O和套接口I/O

1)首先我们需要定义一个集合,随后清空该集合:
fd_set rset;
FD_ZERO(&ret);
2)然后写一个死循环去检测I/O是否产生可读事件。
如下客户端通信模块代码:

(3)回射客户端/服务器源码

echocli.c

附echosrv.c

小结:
(1)select可以同时检测标准输入以及套接口读事件。
(2)对等方发来的数据,客户端可以通过select进行及时处理,而不会因为程序阻塞在标准输入而无法对套接口进行读操作。
(3)当要处理多个I/O,又想用单进程的方式进行处理时,用select比较方便。因为单进程可以轮询这些事件,对一个个事件可以进行相应的处理。

发表评论

电子邮件地址不会被公开。 必填项已用*标注