关于select , pselect , poll
来源:互联网 发布:淘宝上门取件在哪里 编辑:程序博客网 时间:2024/05/22 09:47
处理多个输入的时候,需要使得内核一旦发现进程指定的一个或多个I/O条件就绪(即输入准备好被读取,或描述符已能承接更多输出),它就通知进程
此即为 I/O 复用
应用场合:
1、客户处理多个描述符时(交互式输入+网络套接字)
2、客户同时处理多个套接字
3、一个TCP服务器既要处理监听套接字,又要处理已连接套接字
4、服务器既要处理TCP,又要UDP
5、服务器处理多个服务或多个协议
因此我们将客户端读取函数改成这样:
批量输入:
采用一问一答的方式虽然很符合回射客户端/服务器的要求,但是既然套接字是全双工通信,我们可以将标准输入和标准输出替换为文件,充分利用。
但是,最后发现输出文件总是小于输入文件(对于回射服务器,他们理应相等)
问题在于我们对标准输入中的EOF的处理
str_cli函数就此返回到main函数,而main函数随后终止。然而在这种批量方式下,标准输入中的EOF并不意味着我们同时也完成了从套接字的读入:可能仍有请求在去往服务器的路上,或仍有请求在返回客户端的路上
我们需要的是一种关闭TCP连接其中一半的方法。也就是说,我们想给服务器TCP发送一个FIN,告诉服务器我们已经完成了发送数据,但是仍然保持该套接字描述符打开以便读取
P135 讲述了select 与缓冲区之间的微妙处理关系,但我没怎么看懂
终止网络连接常用close,不过close有两个限制,却可以使用shutdown函数来避免。
1、close把描述符的引用计数减1,仅在该计数为0时关闭描述符。使用shutdown可以不管引用计数就激发TCP的正常连接终止序列(由FIN开始的4个字节)
2、close终止读和写两个方向的数据传输。既然TCP是全双工的,有时候我们需要告知对端我们已经完成了数据发送,即使对端仍有数据要发送给我们。即上面批量输入遇到的问题。
修改后,如下:
既然客户端用select函数进行了更改,服务器自然也可以:
关于 POLL
使用poll改写服务器:
此即为 I/O 复用
应用场合:
1、客户处理多个描述符时(交互式输入+网络套接字)
2、客户同时处理多个套接字
3、一个TCP服务器既要处理监听套接字,又要处理已连接套接字
4、服务器既要处理TCP,又要UDP
5、服务器处理多个服务或多个协议
int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); (一) struct timeval { long tv_sec ; //sec long tv_usec; //micro-sec }; 1、为空: 永远等下去 2、等待一段固定时间 3、根本不等待 定时器值必须为 0 (二) 支持的异常条件: 1、某个套接字的带外数据到达 2、某个已置为分组模式的伪终端存在可从其主端读取的控制状态信息 (三) 要用到的宏函数: 1、void FD_ZERO(fd_set *fdset) //clear all 2、void FD_SET(int fd,fd_set *fdset) //turn on 3、void FD_CLR(int fd,fd_set *fdset) //turn off 4、int FD_ISSET(int fd,fd_set *fdset) //test (四) 该函数返回后,用FD_ISSET来测试描述符,描述符集内任何与未就绪描述符对应的位返回时均清成0. 为此,每次重新调用select函数,都得【再次】把所有所关心的描述符位置一 (五) 返回已就绪的总位数
因此我们将客户端读取函数改成这样:
void str_cli(FILE *fp, int sockfd){ int maxfdpl; fd_set reset; char sendling[MAX],receive[MAX]; FD_ZERO(reset); for(;;) { FD_SET(fileno(fp),&reset); FD_SET(sockfd, &reset); maxfdlp = max(fileno(fp),sockfd)+1; Select(maxfdlp, &reset,NULL,NULL,NULL); if(FD_ISSET(sockfd,&rerset)) { if(Readline(sockfd,receive,MAX) == 0) err_quit(); Fputs(buf,stdout); } if(FD_ISSET(fileno(fp),&reset)) { if(Fgets() == NULL) return; Writen(sockfd,sending,strlen(sending)); } }}
批量输入:
采用一问一答的方式虽然很符合回射客户端/服务器的要求,但是既然套接字是全双工通信,我们可以将标准输入和标准输出替换为文件,充分利用。
但是,最后发现输出文件总是小于输入文件(对于回射服务器,他们理应相等)
问题在于我们对标准输入中的EOF的处理
str_cli函数就此返回到main函数,而main函数随后终止。然而在这种批量方式下,标准输入中的EOF并不意味着我们同时也完成了从套接字的读入:可能仍有请求在去往服务器的路上,或仍有请求在返回客户端的路上
我们需要的是一种关闭TCP连接其中一半的方法。也就是说,我们想给服务器TCP发送一个FIN,告诉服务器我们已经完成了发送数据,但是仍然保持该套接字描述符打开以便读取
P135 讲述了select 与缓冲区之间的微妙处理关系,但我没怎么看懂
终止网络连接常用close,不过close有两个限制,却可以使用shutdown函数来避免。
1、close把描述符的引用计数减1,仅在该计数为0时关闭描述符。使用shutdown可以不管引用计数就激发TCP的正常连接终止序列(由FIN开始的4个字节)
2、close终止读和写两个方向的数据传输。既然TCP是全双工的,有时候我们需要告知对端我们已经完成了数据发送,即使对端仍有数据要发送给我们。即上面批量输入遇到的问题。
int shutdown(int sockfd, int how) 参数howto: A、SHUT_RD 关闭连接的读这一半 ---- 套接字中不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃。进程不能再对这样的套接字调用任何读函数,对一个TCP套接字调用该操作后,由该套接字接收的来自对端的任何数据都被确认,然后悄然丢弃。。。 B、SHUT_WR 关闭写这一半 ----- 对于TCP套接字,这称为半关闭。当前留在套接字发送缓冲区的数据将被发送掉,后跟TCP正常终止序列。不管套接字描述符的引用计数是否为0,写半部分关闭照样执行,不能再对这个套接字调用任何写函数 C、SHUT_RDWR 都关闭
修改后,如下:
void str_cli(FILE *fp, int sockfd){ int maxfdlp, stdineof; fd_set rset; int nbyte; FD_ZERO(&rset); stdineof = 0; for(;;) { maxfdlp = max(fileno(fp),sockfd)+1; if(stdinof == 0) FD_SET(fileno(fp), &rset); FD_SET(sockfd, &rset); if(select(maxfdlp,&rset,NULL,NULL,NULL) < 0) { perror("select"); exit(1); } if(FD_ISSET(sockfd, &rset)) { if((nbyte=read(sockfd,buf,MAXLEN)) == 0) { if(stdineof == 1) return; //normal end else { //service send FIN to this client unexpectedly fprintf(stderr,"Service Terminated Unexpected\n"); exit(1); } } else if(nbyte < 0) { perror("Read Error"); exit(1); } if(write(1,buf,nbyte) != nbyte) { perror("Write Error"); exit(1); } } if(FD_SET(fileno(fp),&rset)) { if((nbyte=read(fileno(fp),buf,MAXLEN)) == 0) { stdineof = 1; shutdown(sockfd, SHUT_WR); //send FIN to service to close half-TCP about write //which means we would not send data anymore. FD_CLR(fileno(fp),&rset); continue; } else if(nbyte < 0) { perror("Read Error"); exit(1); } if(written(sockfd,buf,nbyte) != nbyte) { perror("Write Error"); exit(1); } } }}
既然客户端用select函数进行了更改,服务器自然也可以:
#include "unp.c"int main(int ac, char *av[]){ struct sockaddr_in serv_addr,clin_addr; int serv_fd, clin_fd; fd_set rset, allset; char table[FD_SETSIZE]; //用一个队列将所有客户的套接字存储下来 int maxi = -1; //对应于拥有最大描述符的数组的索引 int maxfd = -1; //最大描述符(select第一个参数要用) int nready; //select将返回的准备好的描述符个数 int i_loop; socklen_t clin_len; char comm_buf[MAXLEN]; int nread, nwrite; if((serv_fd=socket(AF_INET,SOCK_STREAM,0)) < 0) oops("socket error"); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(13000); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(serv_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) oops("bind error"); if(listen(serv_fd, 10) < 0) oops("listen error"); maxfd = serv_fd; //当前最大描述符当然是用于监听的套接字 for(i_loop=0; i_loop<FD_SETSIZE; i_loop++) table[i_loop] = -1; FD_ZERO(&allset); FD_SET(serv_fd,&allset); //设置一个allset,且在循环中令一个变量等于它,就不需要每次都重置了 for(;;) { rset = allset; nready = select(maxfd+1, &rset, NULL, NULL, NULL); if(nready == -1) oops("select error"); if(FD_ISSET(serv_fd,&rset)) { clin_len = sizeof(clin_addr); if((clin_fd=accept(serv_fd, (struct sockaddr *)&clin_addr, &clin_len)) < 0) oops("accept error"); for(i_loop=0; i_loop<FD_SETSIZE; i_loop++) //挑一个空位给这个新套接字 if(table[i_loop] < 0) { table[i_loop] = clin_fd; break; } FD_SET(clin_fd,&allset); //并将这个新套接字设置进这个组中 if(maxfd < clin_fd) maxfd = clin_fd; if(maxi < i_loop) //取到最大的下标,用于接下来的循环 maxi = i_loop; if(--nready == 0) //若等于0了,就无需再经历下面的循环 continue; } for(i_loop=0; i_loop<=maxi; i_loop++) { if(table[i_loop] < 0) continue; if(FD_ISSET(table[i_loop], &rset)) { if((nread=read(table[i_loop], comm_buf, MAXLEN)) > 0) if(written(table[i_loop], comm_buf, nread) != nread) oops("write error"); if(nread < 0) oops("read error") else if(nread == 0) //connection closed by client { close(table[i_loop]); FD_CLR(table[i_loop],&allset); table[i_loop] = -1; } if(maxi == i_loop) { maxfd = -1; for(i_loop=0; i_loop<=maxi; i_loop++) if(maxfd < table[i_loop]) maxfd = table[i_loop]; } if(--nready == 0) //a better solution break; } } }}
// here I try pselect instead of select// Given that one global variation "intr_flag" will be changed if signal "SIGINT" occures// and the global variation will be used/* sigset_t set,nset,oset; sigemptyset(&nset); sigemptyset(&set); sigaddset(&nset,SIGINT); sigprocmask(SIG_BLOCK,&nset,&oset); if(int_flag) handle_intr(); //handle the signal if((nready = pslect(.....,&set)) == 0) { if(errno == EINTR) if(intr_flag) handle_intr(); }*//*关于信号处理的部分,之前在APUE中已经有了一定的概念。在测试intr_flag变量之前,我们阻塞SIGINT,当pselect被调用时,它先以空集代替进程的信号掩码,再检查描述符,并可能进入睡眠;当pselect返回时,进程的信号掩码又被重置为调用pselect之前的值*/
关于 POLL
int poll(struct pollfd *fdarray,long nfds,int timeout);struct pollfd{ int fd; short events; short revents;};就TCP和UDP套接字而言,以下条件引起poll返回特定的revent: 1、所有正规TCP数据和所有UDP数据都被认为是普通数据 2、TCP带外数据被认为是优先级数据 3、当TCP连接的读半部关闭时,也被认为是普通数据,随后读操作返回0 4、TCP连接存在错误既可认为是普通数据,也可以是错误。无论如何,读操作返回-1,并设置errno。可用于处理RST或超时等条件 5、在监听套接字上有新的连接可用既可以是普通数据(大多数),也可以是优先数据 6、非阻塞式connect的完成被认为是使相应套接字可写timeout: 1、INFTIM 永远等待 2、 0 立即返回 3、 >0 等待指定数目的毫秒
使用poll改写服务器:
#include "unp.h"#define OPEN_MAX 100#define INFTIM -1int main(int ac, char *av[]){ struct sockaddr_in serv_addr,clin_addr; struct pollfd client[OPEN_MAX]; //poll要使用的参数 int serv_fd, clin_fd; int nready; //select将返回的准备好的描述符个数 int maxi; int i_loop; socklen_t clin_len; char comm_buf[MAXLEN]; int nread, nwrite; if((serv_fd=socket(AF_INET,SOCK_STREAM,0)) < 0) oops("socket error"); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(13000); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(serv_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) oops("bind error"); if(listen(serv_fd, 10) < 0) oops("listen error"); client[0].fd = serv_fd; client[0].events = POLLRDNORM; for(i_loop=0; i_loop<OPEN_MAX; i_loop++) client[i_loop].fd = -1; //poll会自动忽略fd为-1的参数 maxi = 0; for(;;) { if((nready=poll(client, maxi+1, INFTIM)) == -1) oops("poll error"); if(client[0].revents & POLLRDNORM) //检测是否有普通数据 { clin_len = sizeof(clin_addr); if((clin_fd=accept(serv_fd, (struct sockaddr *)&clin_addr, &clin_len)) == -1) oops("accept error"); for(i_loop=0;i_loop<OPEN_MAX;i_loop) if(client[i_loop].fd < 0) { client[i_loop].fd = clin_fd; client[i_loop].events = POLLRDNORM; } if(i_loop == OPEN_MAX) { fprintf(stderr,"too many descriptors\n"); exit(1); } if(i_loop > maxi) maxi = i_loop; if(--nready <= 0) continue; } for(i_loop=1; i_loop<=maxi; i_loop++) //0固定为监听用的,之前select从0开始是因为数组元素全是存客户套接字的 { if(client[i_loop].fd < 0) continue; if(client[i_loop].revents & (POLLRDNORM | POLLERR) ) { if((nread=read(client[i_loop].fd, comm_buf, MAXLEN)) > 0) if(written(client[i_loop].fd, comm_buf, nread) != nread) oops("write error"); if(nread < 0) { if(errno == ECONNRESET) { close(client[i_loop].fd); client[i_loop].fd = -1; } else { perror("read error"); exit(1); } } else if(nread == 0) //connection closed by client { close(client[i_loop].fd); client[i_loop].fd = -1; } } if(--nready <= 0) break; } } return 0;}//这样看下来,其实差别不大,但之前select使用的数组不再需要了,简化了些
0 0
- 关于select , pselect , poll
- select pselect poll
- select,pselect,poll函数
- linux select pselect poll 异同
- select pselect poll ppoll epoll
- select、pselect、poll和epoll的区别
- I/O复用:select、poll、pselect和epoll
- UNIX环境高级编程——I/O多路转接(select、pselect和poll)
- I/O多路转接(select、pselect和poll)使用
- I/O复用(I/O multiplexing): select, pselect, poll, ppoll, epoll
- UNIX编程专题-I/O复用:select、pselect、poll和epoll解析
- select()与pselect()
- select() vs pselect()
- select和pselect
- select和pselect区别
- io复用select pselect
- 深入研究socket编程(2)——I/O多路转接(select、pselect和poll)
- select 和pselect的区别
- string、Empty和null三者的区别
- guava中Range的使用方法(com.google.common.collect.Range)
- 计算机专业该不该考研?
- 最大子序列乘积
- 跟踪分析Linux内核的启动过程|Linux内核分析 第三周作业 - 网易云课堂
- 关于select , pselect , poll
- java中值传递解析
- JPA的查询语言—JPQL的关联查询
- JS正则表达式基础
- 数据库基本架构与日志的作用
- 巩固java(一)----java与对象
- HDU 5190-Go to movies
- 用rdiff拆分合并文件
- 【FZU】2184 逆序数还原(线段树)