I/O复用函数的使用——select
生活随笔
收集整理的這篇文章主要介紹了
I/O复用函数的使用——select
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
I/O 復用使得程序能同時監聽多個文件描述符,這對于提高程序的性能至關重要。通常,網絡程序在下列情況下需要使用 I/O 復用技術:
? TCP 服務器同時要處理監聽套接字和連接套接字。
? 服務器要同時處理 TCP 請求和 UDP 請求。
? 程序要同時處理多個套接字。
? 客戶端程序要同時處理用戶輸入和網絡連接。
? 服務器要同時監聽多個端口。
需要指出的是,I/O 復用雖然能同時監聽多個文件描述符,但它本身是阻塞的。并且當多個文件描述符同時就緒時,如果不采取額外的措施,程序就只能按順序依處理其中的每一個文件描述符,這使得服務器看起來好像是串行工作的。如果要提高并發處理的能力,可以配合使用多線程或多進程等編程方法。
1.select 的接口介紹
select 系統調用的用途是:在一段指定時間內,監聽用戶感興趣的文件描述符的可讀、
可寫和異常等事件。
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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: socket网络编程——多进程、多线程处
- 下一篇: I/O复用函数的使用——poll