1. I/O模型
(1)阻塞式I/O
(2)非阻塞式I/O
通过指定recvfrom的flag参数为 MSG_DONTWAIT,当没接收到数据时会直接返回而不阻塞,并设置errno为 EWOULDBLOCK
轮询/循环调用recvfrom直到有数据就绪,但会占用很多CPU时间
(3)I/O复用(select、poll)
进程在select上阻塞,直到有数据就绪了就去调用recvfrom确保recvfrom一定能读取到数据。
相比前面的两个,select的优势在于可以等待多个描述符(类似的做法:用多线程+阻塞I/O,每个线程处理一个文件描述符,这样线程调用recvfrom阻塞不会影响其他的线程)
(4)信号驱动式I/O
通过注册信号处理函数,让内核在描述符就绪时发送SIGIO信号,然后调用recvfrom确保能读取到数据
(5)异步I/O
与信号驱动的区别:信号驱动式是内核通知进程什么时候启动具体的I/O操作,而异步I/O是内核通知进程I/O操作何时完成
1 struct aiocb { 2 int aio_fildes; //文件描述符 3 off_t aio_offset; //文件偏移量 4 volatile void *aio_buf; //缓冲区 5 size_t aio_nbytes; // 6 int aio_reqprio; //请求优先级 7 struct sigevent aio_sigevent; //通知方式 8 int aio_lio_opcode;// 9 };
2. I/O复用
(1)select
返回值:-1,出错;0,没有设备准备好;大于0,准备好的设备的个数
nfds:关注的描述符的个数(值为最大描述符+1)
timeout:等待集合中任意一个描述符就绪/可用的时间
设为NULL:一直等待,当有一个描述符准备好I/O时select才返回
指定秒、微秒:如果没有一个描述符准备好,则等待该时间后返回
timeval结构不为NULL,但秒和微秒设为0:不等待,检查描述符后立即返回
struct timeval { long tv_sec; //秒 long tv_usec; //微秒(10^-6秒) }; struct timespec { long tv_sec; long tv_nsec; //纳秒(10^-9秒) };
readset、writeset、excepset:读、写、异常描述符集合
每个参数是一个整型数组,每个元素的每一位对应一个描述符(若int为32位,则第一个元素对应了描述符0~31)
描述符集合测试宏:
FD_CLR:将fd在fdset中对应的位置0
FD_SET:将fd在fdset中对应的位置1
FD_ZERO:fdset中所有位清零
FD_ISSET:检查fd对应的位是否置1
调用select后,我们关注的但是还未就绪的描述符对应的位会被清零,因此每次重新调用select时,需要再次把关注的描述符对应位置1
(2)pselect
最后一个参数sigmask:
屏蔽信号,在pselect返回后,再将屏蔽的信号恢复(原子操作),不用单向pselect调用时被信号中断
不能屏蔽SIGKILL
(3)poll
独立为每一个要监控的文件描述符建立一个进而构体pollfd(描述了监控的行为、事件)
1 struct pollfd { 2 int fd; 3 short events; //对fd关注的事件 4 short revents; //fd上发生的事件 5 };
events、revents:events可设为读写标志,不能设为异常标志 revents为传出参数
timeout:
INFTIM:永远等待,直到①fds数组中有一个文件描述符准备好了,此时返回1 或②捕捉到信号返回,返回值-1,置错误码errno poll返回
0:不等待,函数检测fds数组中所有成员后返回
大于0,等待timeout时间
(4)ppoll
(5)效率更好的 epoll [solaris上有 /dev/poll FreeBSD有kqueue]
select、poll都是对所有关注的套接字进行检测,(从用户态向内核太复制了大量数据)当连接数很大时效率低
3. select的简单例程
假设:服务器一个监听套接字,最大监听5个连接。
select检测监听套接字和连接套接字:如果监听套接字有新连接时就建立连接,将连接描述回复保存连到一个数组(数组大小为5,因为listen5个)。
如果已连接的描述符有数据准备好了,就读写数据
下面只写了关键部分,代码并不全:
1 int serverfd; 2 int listennum = 5; 3 servefd = socket(...); 4 bind(serverfd,...) 5 listen(serverfd,listennum); 6 7 fd_set client_fdset; 8 int maxsock = serverfd; 9 int client_sockfd[5]; bzero(client_sockfd); 10 int conn_amount = 0; 11 12 while(1) { 13 //select前的初始化 14 FD_ZERO(&client_fdset); 15 FD_SET(serverfd,&client_fdset); //监控描述符加入 16 for(int i=0;i<5;i++) { 17 if(client_sockfd[i]!=0) { 18 FD_SET(client_sockfd[i],&client_fdset); //连接描述符加入 19 } 20 } 21 int ret = select(maxsock+1,&client_fdset,NULL,NULL,&timeva); 22 23 for(int i=0;i<conn_amount;i++) { 24 //检查连接套接字是否能读 25 if(FD_ISSET(client_sockfd[i],&client_fdset)) { 26 ret = recv(client_sockfd[i],buf,1024,0); //能读立刻读 27 if(ret<=0) { 28 close(lient_sockfd[i]); 29 FD_CLR(client_sockfd[i],&client_fdset); 30 client_sockfd[i] = 0; 31 } 32 } 33 } 34 //检查是否有新连接,有就接收连接,加入client_sockfd中 35 if(FD_ISSET(serverfd,&client_fdset)) { 36 int sock_client = accept(serverfd...); 37 if(conn_amount<listennum) { 38 client_sockfd[conn_amount++] = sock_client; 39 if(sock_client>maxsock) { 40 maxsock = sock_client; 41 } 42 } 43 } 44 45 }
4. epoll的基本原理