记录下简单的select的使用。以防忘记。
服务端代码
#include <stdio.h> #include "unp.h" int main() { int i,maxi,maxfd,listenfd,clifd; int readycnt,client[FD_SETSIZE]; int n; char buff[MAXLINE]; //这里rset是select的监听列表。但是因为每次循环完都要重新加入监听列表。所以我们把要监听的数据放到allset。然后把allset赋值给rset fd_set rset,allset; //服务端套接口创建,绑定,启动监听 struct sockaddr_in serAddr; bzero(&serAddr,sizeof(serAddr)); serAddr.sin_family=AF_INET; serAddr.sin_port=htons(SERV_PORT); serAddr.sin_addr.s_addr=htonl(INADDR_ANY); listenfd=Socket(AF_INET,SOCK_STREAM,0); socklen_t len=sizeof(serAddr); Bind(listenfd,(SA *)&serAddr,len); Listen(listenfd,LISTENQ); //初始化select要监听的最大文件描述符id,现在还没有接收客户端,所以最大肯定是服务端监听端口 maxfd=listenfd; maxi=-1; //客户端的连接数不能超过FD_SETSIZE。所有套接口描述符都将装在client数组里面。默认没有填充的值为-1.这里初始化一下 for(i=0;i<FD_SETSIZE;i++){ client[i]=-1; } FD_ZERO(&allset); //初始化监听列表 FD_SET(listenfd,&allset); //将服务端端口监听的套接口放入select的监听列表 for(;;){ rset=allset; //所有要监听和取消监听都是操作allset。然后开始的时候把allset赋值给rset readycnt=Select(maxfd+1,&rset,NULL,NULL,NULL); //开始监听 if(FD_ISSET(listenfd,&rset)){ //如果是服务端端口的套接口收到消息,说明是有客户端连接上来 clifd=Accept(listenfd,(SA *)NULL,NULL); //接收客户端连接 for(i=0;i<FD_SETSIZE;i++){ //遍历存放客户端连接套接口的数组。找到一个-1的就是可以存放的位置 if(client[i]==-1) break; } if(i==FD_SETSIZE) //如果找到结束都没有。则说明所有队列满了 err_quit("too many clients"); client[i]=clifd; //记录客户端套接口 FD_SET(clifd,&allset); //加入select的监听队列 if(clifd>maxfd) //更新最大文件描述符 maxfd=clifd; if(i>maxi) //更新最高的位置信息。可以让后面不用完整遍历 maxi=i; printf("cli connect success maxi:%d \r\n",maxi); if(--readycnt<=0) //这个就绪套接口处理完成,递减一下。如果已经没其他的了。就不用继续往后遍历了 break; } } } }
客户端例子
#include <stdio.h> #include "unp.h" void send_str(int sockfd,int stdinfd){ fd_set rset; //监听的列表 FD_ZERO(&rset); //重置 int stdinof=0; //标记标准输入是否关闭了 int n; char buff[MAXLINE]; for(;;){ if(stdinof==0) //标准输入如果关闭了就不要监听了 FD_SET(stdinfd,&rset); FD_SET(sockfd,&rset); //监听套接口 //select第一个参数是要用最大文件描述符+1.我们监听的是标准输入和套接口。标准输入输出是0和1。套接口描述符肯定要大。所以直接填sockfd+1 Select(sockfd+1,&rset,NULL,NULL,NULL); //阻塞,等待通信管道准备就绪 if(FD_ISSET(stdinfd,&rset)){ //判断是否是io管道就绪了 if((n=Read(stdinfd,buff,MAXLINE))==0){ //接收输入结果,如果结果是0,就代表管道关闭了 stdinof=1; //设置后上面不再监听 Shutdown(sockfd,SHUT_WR); //半关闭写入接口,服务端就会read到0.然后关闭和客户端的连接 FD_CLR(stdinfd,&rset); //从监听列表移除 continue; } Writen(sockfd,buff,n); //正常情况就把标准输入的发送给服务端 } if(FD_ISSET(sockfd,&rset)){ //如果是服务端的套接口有数据了 if((n=Read(sockfd,buff,MAXLINE))==0){ //读取服务端返回数据 printf("sock read=0\r\n"); if(stdinof==1) //如果标准输入已经关掉了,就不用再往下走了 return; else err_quit("ser close\r\n"); //其他服务端关掉的情况打印消息并退出 } Write(STDOUT_FILENO,buff,n); //正常情况就写入到标准输出 } } } int main(int argc,char** argv) { if(argc!=2) err_quit("argc err"); char *addr=argv[1]; //链接服务端 struct sockaddr_in serAddr; serAddr.sin_family=AF_INET; serAddr.sin_port=htons(SERV_PORT); Inet_pton(AF_INET,addr,&serAddr.sin_addr); int sockfd=Socket(AF_INET,SOCK_STREAM,0); Connect(sockfd,(SA*)&serAddr,sizeof(serAddr)); //链接成功后的业务逻辑 send_str(sockfd,STDIN_FILENO); exit(0); }
select还有两点最容易出错的地方,
1、是忘记对最大描述字+1。也就是select的第一个参数经常会出错
2、忘记描述字集是值-结果参数。也就是rset里面是fd-结果参数。所以rset总是要重置来再次监听。因为之前设置的1又变回0了。