socket编程(8)
本章目标:
- 五种I/O模型
- select
- 用select改进回射客户端程序
一、五种I/O模型
- 阻塞I/O
- 非阻塞I/O
- I/O复用 (select和poll)
- 信号驱动I/O
- 异步I/O
阻塞I/O
非阻塞I/O
消耗cpu,一般很少使用。
I/O复用
select管理文件描述符集合。
信号驱动I/O
是一种 拉 模式
异步I/O
是一种 推 模式
二、select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数:nfds为最大文件描述符+1
中间三个参数为描述符集合,如果我们对某一个条件不感兴趣,可以把它设置为NULL;
注意:后面四个参数都是 输入输出参数。
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
原理:
select函数修改由指针readfds, writefds, excepfds, 所指向的描述符集合,因而这三个描述符集合是输入输出参数。
调用该函数时,我们指定所关心的描述符的值,该函数返回时,结果将指示哪些描述符已经就绪。
函数返回后,我们使用FD_ISSET宏来测试fd_set 集合中的描述符。描述符集合中任何与未就绪描述符对应的位返回时将清成 0 。
为此,每次重新调用select函数时,我们都得再次把所有描述符集合内关心的位均置为1 。
该函数返回时值表示跨所有描述符集合的已就绪的总位数。如果在任何描述符就绪之前定时器到时,将返回 0 ;返回 -1 表示出错。(比如中断等)。
总结一下:
select就像一个管理者
用select管理多个I/O
一旦其中的一个I/O或者多个I/O检测到我们所关心的事件,select函数返回,返回值为检测到的事件数、
并且返回哪些I/O发生了事件
遍历这些事件,进而处理事件。
三、用select改进回射客户端程序
代码:
void echo_cli(int sockfd) { /* char sendbuf[1024]={0}; char recvbuf[1024]={0}; while(fgets(sendbuf,sizeof(sendbuf),stdin) != NULL) { writen(sockfd,sendbuf,strlen(sendbuf)); int ret = readline(sockfd,recvbuf,sizeof(recvbuf)); if(ret == 0) { printf("client close\n"); break; } fputs(recvbuf,stdout); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } close(sockfd); */ fd_set rset; FD_ZERO(&rset); int nready;//检测到的事件个数 int maxfd; /*//如果标准输入重定向,那么它可能不是0,所以这里用fileno球它的文件描述符*/ int fd_stdin = fileno(stdin); if(fd_stdin > sockfd) maxfd = fd_stdin; else maxfd = sockfd; char sendbuf[1024]={0}; char recvbuf[1024]={0}; while(1) { /*因为集合中的数据是会发生改变的,所以不可以把下面两句放到循环外*/ FD_SET(fd_stdin,&rset); FD_SET(sockfd,&rset); nready = select(maxfd+1,&rset,NULL,NULL,NULL); if(nready == -1)//出错 ERR_EXIT("select"); if(nready == 0)//出错 continue; if(FD_ISSET(sockfd,&rset)) //套接口产生了可读 { /*读取*/ int ret = readline(sockfd,recvbuf,sizeof(recvbuf)); if(ret == 0) { printf("server close\n"); break; } fputs(recvbuf,stdout); memset(recvbuf,0,sizeof(recvbuf)); } if(FD_ISSET(fd_stdin,&rset)) //标准输入有数据 { if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL) break; writen(sockfd,sendbuf,strlen(sendbuf)); memset(sendbuf,0,sizeof(sendbuf)); } } close(sockfd); }
没有用select之前,先断开服务器端的(服务器端close),那么网络状态为:
由于客户端阻塞在fgets(),导致进行不到 read函数。所以状态无法向后进行。
使用了select之后,网络状态为