日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

I/O复用函数的使用——select

發布時間:2024/4/17 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 I/O复用函数的使用——select 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

I/O 復用使得程序能同時監聽多個文件描述符,這對于提高程序的性能至關重要。通常,網絡程序在下列情況下需要使用 I/O 復用技術:
? TCP 服務器同時要處理監聽套接字和連接套接字。
? 服務器要同時處理 TCP 請求和 UDP 請求。
? 程序要同時處理多個套接字。
? 客戶端程序要同時處理用戶輸入和網絡連接。
? 服務器要同時監聽多個端口。
需要指出的是,I/O 復用雖然能同時監聽多個文件描述符,但它本身是阻塞的。并且當多個文件描述符同時就緒時,如果不采取額外的措施,程序就只能按順序依處理其中的每一個文件描述符,這使得服務器看起來好像是串行工作的。如果要提高并發處理的能力,可以配合使用多線程或多進程等編程方法。

1.select 的接口介紹

select 系統調用的用途是:在一段指定時間內,監聽用戶感興趣的文件描述符的可讀、
可寫和異常等事件。
select 系統調用的原型如下:

#include <sys/select.h>int select( int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct ti meval *timeout); /* select 成功時返回就緒(可讀、可寫和異常)文件描述符的總數。如果在超時時間內沒有任何文件描述符就緒,select 將返回 0。select 失敗是返回-1.如果在 select 等待期間,程序接收到信號,則 select 立即返回-1,并設置 errno 為 EINTR。 maxfd 參數指定的被監聽的文件描述符的總數。它通常被設置為 select 監聽的所有文件描述符中的最大值+1 readfds、writefds 和 exceptfds 參數分別指向可讀、可寫和異常等事件對應的文件描述符集合。應用程序調用 select 函數時,通過這 3 個參數傳入自己感興趣的文件描述符。 select 返回時,內核將修改它們來通知應用程序哪些文件描述符已經就緒 *///fd_set 結構如下: #define __FD_SETSIZE 1024 typedef long int __fd_mask; #define __NFDBITS (8 * (int) sizeof (__fd_mask))typedef struct {#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];define __FDS_BITS(set) ((set)->fds_bits)#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];# define __FDS_BITS(set) ((set)->__fds_bits)#endif } fd_set;//通過下列宏可以訪問 fd_set 結構中的位: FD_ZERO(fd_set *fdset); // 清除 fdset 的所有位 FD_SET(int fd, fd_set *fdset); // 設置 fdset 的位 fd FD_CLR(int fd, fd_set *fdset); // 清除 fdset 的位 fd int FD_ISSET(int fd, fd_set *fdset);// 測試 fdset 的位 fd 是否被設置/* timeout 參數用來設置 select 函數的超時時間。它是一個 timeval 結構類型的指針,采用指針參數是因為內核將修改它以告訴應用程序 select 等待了多久。 */ timeval結構的定義如下: struct timeval {long tv_sec; //秒數long tv_usec; // 微秒數 }; /* 如果給 timeout 的兩個成員都是 0,則 select 將立即返回。如果 timeout傳遞NULL,則 select 將一直阻塞,直到某個文件描述符就緒 */


select過后,fdset只保留就緒事件的fd,比如圖中selest過后,fd第三個和第六個位(在這里位從下標0開始,就像數組一樣)是1,就說明描述符為3和6的有就緒事件需要處理。

1.select 的示例代碼

TCP服務端代碼

#include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<sys/socket.h> #include<arpa/inet.h> #include<sys/select.h>#define MAX_FD 1024 #define DATALEN 128//初始化服務器的socket套接字 int InitSocket() {int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd != -1);struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));assert(res != -1);res = listen(sockfd,5);assert(res != -1);return sockfd; }//初始化記錄服務器套接字的數組 void InitFds(int fds[],int n) {for(int i = 0; i < n; i++){fds[i] = -1;} }//將套接字描述符添加到數組中,所有新的描述符都被存放到數組fds中 void AddFdToFds(int fds[],int n, int fd) {for(int i = 0; i < n; i++){if(fds[i] == -1){fds[i] = fd;break;}} }//刪除數組中的套接字描述符,已關閉的客戶端的套接字描述符從數組fds中刪除 void DelFdFromFds(int fds[],int n, int fd) {for(int i = 0; i < n; i++){if(fds[i] == fd){fds[i] == -1;break;}} }//將數組中的套接字描述符設置到fd_set變量中,并返回當前最大的文件描述符值(將fds中的所有套接字描述符都映射到fd_set對應的位中,將對應的位置為1) int SetFdToFdset(fd_set* fdset,int fds[],int n) {FD_ZERO(fdset);int maxfd = fds[0];for(int i = 0; i < n; i++){if(fds[i] != -1){FD_SET(fds[i],fdset);//將fds中的套接字描述符都映射到對應的fdset的位中maxfd = maxfd > fds[i] ? maxfd : fds[i];}}return maxfd; }//接收客戶端連接,如果就緒事件的套接字描述符等于sockfd就說明有客戶端連接 void GetClientLink(int fds[],int n, int sockfd) {struct sockaddr_in caddr;memset(&caddr,0,sizeof(caddr));int len = sizeof(caddr);int c = accept(sockfd,(struct sockaddr*)&caddr,&len);if(c < 0){return;}printf(" cilent %d link sucess\n",c);AddFdToFds(fds,n,c); }//處理客戶端數據,如果有就緒事件,且套接字描述符不等于sockfd,就說明有客戶端發送消息 void DealClientData(int fds[],int n,int clifd) {char data[DATALEN] = {0};int num = recv(clifd,data,DATALEN-1,0);if(num <= 0)//說明客戶端關閉{DelFdFromFds(fds,n,clifd);//從fds中刪除客戶端套接字描述符close(clifd);printf("a client game over\n");}else{printf("%d:%s\n",clifd,data);send(clifd,"OK",2,0);} }//處理select返回的就緒事件 void DealReadyEvent(int fds[],int n,fd_set* fdset,int sockfd) {for(int i = 0; i < n; i++){ //此時有就緒事件的套接字描述符對應的fdset的位中,被置為1,通過遍歷fds,和FD_ISS方法,找到這些就緒事件對應的套接字描述符,對其事件進行處理if(fds[i] != -1 && FD_ISSET(fds[i],fdset)){if(fds[i] == sockfd)//說明sockfd有就緒事件,說明有客戶端連接{GetClientLink(fds,n,sockfd);}else//說明已連接的客戶端有發送數據,我們進行處理{DealClientData(fds,n,fds[i]);}}} }int main() {int sockfd = InitSocket();assert(sockfd != -1);fd_set readfds;int fds[MAX_FD];//fds儲存套接字描述符InitFds(fds,MAX_FD);//將fds都置為-1,套接字描述符大于0,所以到時候知道fds上的-1就是沒有儲存套接字描述符AddFdToFds(fds,MAX_FD,sockfd);//將套接字描述符sockfd加入到fds中while(1){ //maxfd對select有用,因為select需要對fd_set對應的位置1,其他位置0,傳入最大套接字描述符,就會告訴select關注前面maxfd+1(+1是因為第二個位記錄的是描述符1)個位就好了int maxfd = SetFdToFdset(&readfds,fds,MAX_FD);struct timeval timeout;timeout.tv_sec = 5;//秒數timeout.tv_usec = 0;//微秒//select過后 ,fdset只保留有就緒事件的套接字描述符對應的位為1int n = select(maxfd+1,&readfds,NULL,NULL,&timeout);//在這里我們只擔心有沒有可讀事件就緒,因為客戶端連接,sockfd為可讀事件,客戶端發送消息,也為可讀事件。把可寫事件和錯誤事件傳入NULL讓select不用關注就好if(n < 0)//select錯誤返回負值{printf("select error\n");break;}else if(n == 0)//規定時間內沒有就緒事件產生,select就會返回0{printf("time out\n");continue;}else//有就緒事件產生,select返回有就緒事件的套接字描述符的總數{DealReadyEvent(fds,MAX_FD,&readfds,sockfd);}}exit(0); }

TCP客戶端代碼

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<assert.h> #include<sys/socket.h> #include<arpa/inet.h>int InitSocket() {int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd != -1);struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));assert(res != -1);return sockfd; }int main() {int sockfd = InitSocket();assert(sockfd != -1);while(1){printf("please input:\n");char buff[128] = {0};fgets(buff,127,stdin);if(strncmp(buff,"end",3) == 0){break;}int n = send(sockfd,buff,strlen(buff)-1,0);if(n < 0){printf("send error\n");break;}memset(buff,0,128);n = recv(sockfd,buff,127,0);if(n < 0){printf("recv error\n");break;}printf("%s\n");}close(sockfd);exit(0);}

總結

以上是生活随笔為你收集整理的I/O复用函数的使用——select的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。