記錄下簡單的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了。