网络高级编程
Linux下主要有4中IO模型
- 阻塞IO:最常用,缺省情况下套接字建立后即处于阻塞IO模式
- 非阻塞IO:可防止进程阻塞在IO操作上,需轮询
- 信号驱动IO:一种异步通讯模型
- IO多路复用:允许同时对多个IO进行控制
在实际应用中,通常是多个客户端连接服务器端的情况。若使用阻塞函数,如果资源没有准备好,则调用该函数的进程将进入睡眠状态,这样就无法处理其他请求了。本节给出了3中解决IO多路复用的方法,分别为非阻塞和异步式处理(使用fcntl()函数)、以及多路复用处理(使用select()函数或poll()函数)。此外还有多进程和多线程,他们都是网络编程中常用的事务处理方法
1. 非阻塞
当我们将一个套接字设置为非阻塞模式,相当于告诉内核:“当我请求的IO操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我”。当应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)
- 使用 fcntl(int fd, int cmd, int arg) 函数来设置非阻塞模式
- cmd 设置为 F_SETFL
- arg 设置为 O_NONBLOCK
/*****设置非阻塞IO模式*****/
int flag = fcntl(sockfd, F_GETFL); //cmd为F_GETFL即返回fd指向的文件的状态
flag |= O_NONBLOCK;
fcntl(sockfd, F_SETFL, flag);
2. 异步IO
使用阻塞式和非阻塞式及多路复用等机制可以有效地进行网络通讯,但效率最高的方法是使用异步通知机制,这种方法在设备IO编程中最常见。内核通过使用异步IO在某一个进程需要处理的时间发生时,向该进程发送一个SIGIO信号。这样应用程序不需要不停地等待某些事件的发生,而可以往下运行,以完成其他工作。只有收到从内核发来的SIGIO信号时,去处理它就可以了
- 使用 fcntl(int fd, int cmd, int arg) 函数来设置异步IO模式
- 使用 fcntl() 函数的 F_SETOWN 命令,使套接字归属于当前进程
- 使用 fcntl() 函数的 F_SETFL 命令,将 arg 设置为 O_ASYNC
/*****设置异步IO模式*****/
fcntl(sockfd, F_SETOWN, getpid()); //将套接字归属于该进程,使内核判断应该向哪个进程发送信号
int flag = fcntl(sockfd, F_GETFL); //cmd为F_GETFL即返回fd指向的文件的状态
flag |= O_ASYNC;
fcntl(sockfd, F_SETFL, flag);
3. 多路复用IO
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;若采用非阻塞模式,对多个输入进行轮询将太浪费CPU时间;若设置多个进程分别处理一条数据链路,新进程间的同步与通讯问题将使程序变得复杂;比较好的方法是使用IO多路复用,其基本思想是:
- 先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回
- 函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作
使用select() / poll() 函数实现多路复用:
/*****select()函数*****/
函数原型:int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
传 入 值:maxfd 所有文件描述符的最大值加1
readfds 所有要读的文件的文件描述符的集合
writefds 所有要写的文件的文件描述符的集合
exceptfds 其他要向我们通知的文件描述符
timeout 超时设置 -->NULL:一直阻塞,直到有文件描述符就绪或出错
-->0:仅仅检测文件描述符集的状态,然后立即返回
-->不为0:在指定时间内,如果没有事件发生,则超时返回
调用 select() 函数时进程会一直阻塞直到有文件可读、有文件可写或者超时时间到。为了设置文件描述符需要使用几个宏:
#include <sys/select.h>
int FD_ZERO(fd_set *fdset); //从fdset中清除所有的文件描述符
int FD_CLR(int fd,fd_set *fdset); //将fd从fdset中清除
int FD_SET(int fd,fd_set *fdset); //将fd加入到fdset
int FD_ISSET(int fd,fd_set *fdset); //判断fd是否在fdset集合中
/*例如*/
fd_set rset;
int fd;
FD_ZERO(&rset);
FD_SET(fd,&rset);
FD_SET(stdin,&rset);
//在select返回之后,可以使用FD_ISSET(fd,&rset)测试给定的位置是否置位。
if(FD_ISSET(fd,&rset))
{
......}
下图为TCP多路复用模型