Linux socket程式設計示例3 select函式的使用
1.select函式簡介
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
當伺服器響應多個客戶端連線的時候,需要定義一個執行緒函式,在每一個執行緒函式裡面處理該連線,進行資料的讀寫,且connect、accept、recv或recvfrom這樣的函式都是阻塞的。
現在想不用執行緒函式就實現伺服器響應多個客戶端的連線,就可以使用select函式,且是非阻塞的,可以查詢是哪個客戶端的響應。
引數maxfd是需要監視的最大的檔案描述符值+1; rdset,wrset,exset分別對應於需要檢測的可讀檔案描述符的集合,可寫檔案描述符的集 合及異常檔案描述符的集合。 struct timeval結構用於描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件發生則函式返回,返回值為0。 fd_set(它比較重要所以先介紹一下)是一組檔案描述字(fd)的集合,它用一位來表示一個fd(下面會仔細介紹),對於fd_set型別通過下面四個巨集來操作: FD_ZERO(fd_set *fdset);將指定的檔案描述符集清空,在對檔案描述符集合進行設定前,必須對其進行初始化,如果不清空,由於在系統分配記憶體空間後,通常並不作清空處理,所以結果是不可知的。 FD_SET(fd_set *fdset);用於在檔案描述符集合中增加一個新的檔案描述符。 FD_CLR(fd_set *fdset);用於在檔案描述符集合中刪除一個檔案描述符。 FD_ISSET(int fd,fd_set *fdset);用於測試指定的檔案描述符是否在該集合中。 2.用select實現伺服器響應多個客戶端的連線。
#include <stdio.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <pthread.h> #include <arpa/inet.h> #include <sys/time.h> int main() { int nSocketFd = 0; int clifd = 0; struct sockaddr_in stServAddr,stClientAddr; socklen_t socketAddrLen; int nRet = 0; char szBuff[BUFSIZ] = {0}; int nReadSocketLen = 0; pthread_t tid; int isReuse = 1; struct timeval tv; int maxfd = 0; int retval = 0; fd_set readfds; int selectFd[100] = {0}; int selectCount = 0; int index = 0; int nRdSocketLen = 0; char szIP[100][20] = {0}; /** 產生一個套介面的描數字 */ nSocketFd = socket(AF_INET,SOCK_STREAM,0); memset(&stServAddr,0,sizeof(struct sockaddr_in)); stServAddr.sin_family = AF_INET; stServAddr.sin_addr.s_addr = htonl(INADDR_ANY); stServAddr.sin_port = htons(8888); setsockopt(nSocketFd,SOL_SOCKET,SO_REUSEADDR,(const char*)&isReuse,sizeof(isReuse)); /** 把這個套接字描述符和本地地址繫結起來 */ nRet = bind(nSocketFd,(struct sockaddr*)&stServAddr,sizeof(stServAddr)); if(-1 == nRet) { perror("bind failed :"); close(nSocketFd); return -1; } /** 設定該套介面的監聽狀態, */ listen(nSocketFd,1024); FD_ZERO(&readfds); FD_SET(nSocketFd,&readfds); tv.tv_sec = 10; tv.tv_usec = 0; maxfd = nSocketFd; while(1) { FD_ZERO(&readfds); FD_SET(nSocketFd,&readfds); tv.tv_sec = 10; tv.tv_usec = 0; maxfd = nSocketFd; /* 把所有的sock描述符都放入到這個描述符集中去 */ for(index = 0;index < selectCount;index++) { FD_SET(selectFd[index],&readfds); if(selectFd[index] > maxfd) { maxfd = selectFd[index]; } } retval = select(maxfd+1,&readfds,NULL,NULL,&tv); /* 出錯 */ if(retval < 0) { perror("select"); } /* 當沒有響應 */ if(retval == 0) { continue; } memset(szBuff,0,BUFSIZ); /* 判斷是哪個客戶端的響應 */ for(index = 0; index < selectCount;index++) { if(FD_ISSET(selectFd[index],&readfds)) { nRdSocketLen = read(selectFd[index],szBuff,BUFSIZ); /* 資料回發 */ write(selectFd[index],szBuff,strlen(szBuff)); if(nRdSocketLen > 0) { printf("read data from %s : %s\n",szIP[index],szBuff); } } } /* 當有新的客戶端連線進來 */ if(FD_ISSET(nSocketFd,&readfds)) { /** 監聽連線,如果有主機要連線過來,則建立套介面連線 */ socketAddrLen = sizeof(struct sockaddr_in); clifd = accept(nSocketFd,(struct sockaddr*)&stClientAddr,&socketAddrLen); if(-1 == clifd) { perror("accept error: "); return -1; } else { selectFd[selectCount] = clifd; /* 把每一個ip地址存放起來*/ strncpy(szIP[selectCount],inet_ntoa(stClientAddr.sin_addr),20); selectCount++; printf("commect %s %d successful\n",inet_ntoa(stClientAddr.sin_addr),ntohs(stClientAddr.sin_port));//ntohs(stClientAddr.sin_port) } } } close(nSocketFd); return 0; }
另一個例程判斷是從終端讀取資料,還是socket發過來的資料。
#include <stdio.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <netdb.h> #include <pthread.h> #include <sys/time.h> int main(int argc,char* argv[]) { int nSocketFd = 0; char szBuff[BUFSIZ] = {0}; struct hostent *pstHostName = NULL; int nRdSocketLen = 0; int nRet = 0; struct sockaddr_in stClientAddr; pthread_t tid; fd_set rfds; struct timeval tv; int maxfd = 0; int retval = 0; int nWrSocketLen = 0; /** 判斷有沒有輸入ip地址 */ if(2 != argc) { printf("please input telnet ipaddress\n"); return -1; } /** 把輸入的主機名變為主機名結構體 */ pstHostName = gethostbyname(argv[1]); if(NULL == pstHostName) { perror("gethostbyname failed\n"); return -1; } /** 獲得socket的檔案描述符 */ nSocketFd = socket(AF_INET,SOCK_STREAM,0); if(-1 == nSocketFd) { perror("create socket failed : "); return -1; } /** 把socket的檔案描述符的結構體清空 */ memset(&stClientAddr,0,sizeof(struct sockaddr_in)); /** 給該結構體賦值 */ stClientAddr.sin_family = AF_INET; stClientAddr.sin_port = htons(8080); stClientAddr.sin_addr =*((struct in_addr *)pstHostName->h_addr); /** 連線目標的主機的ip */ nRet = connect(nSocketFd,(struct sockaddr *)&stClientAddr,sizeof(stClientAddr)); if(-1 == nRet) { perror("connect "); return -1; } else { printf("connect hostname %s successful\n",argv[1]); } if(nSocketFd > maxfd) { maxfd = nSocketFd; } while(1) { FD_ZERO(&rfds); FD_SET(0,&rfds);//設定鍵盤響應 FD_SET(nSocketFd,&rfds); tv.tv_sec = 1; tv.tv_usec = 0; retval = select(maxfd+1,&rfds,NULL,NULL,&tv); if(retval == -1) { perror("select:"); return 0; } if(retval == 0) //沒有響應 { continue; } memset(szBuff,0,BUFSIZ); if(FD_ISSET(0,&rfds)) //是鍵盤響應 { /** 從終端讀取資料 */ read(STDIN_FILENO,szBuff,BUFSIZ); /** 把從終端讀取的資料傳送出去 */ nWrSocketLen = write(nSocketFd,szBuff,strlen(szBuff)); if(nWrSocketLen > 0) { printf("send message successful\n"); } } if(FD_ISSET(nSocketFd,&rfds)) { /** 把伺服器端的資料傳送過來 */ nRdSocketLen = read(nSocketFd,szBuff,BUFSIZ); if(nRdSocketLen > 0) { printf("read data:%s\n",szBuff); } } } return 0; }
--------------------- 作者:dmfrm 來源:CSDN 原文:https://blog.csdn.net/u010889616/article/details/48142483 版權宣告:本文為博主原創文章,轉載請附上博文連結!