Linux select函数用法和原理
select函數的用法和原理
Linux上的select函數
select函數用于檢測一組socket中是否有事件就緒.這里的事件為以下三類:
- 在socket內核中,接收緩沖區中的字節數大于或者等于低水位標記SO_RCVLOWAT,此時調用rec或read函數可以無阻塞的讀取該文件描述符,并且返回值大于零
- TCP連接的對端關閉連接,此時本端調用rrecv或read函數對socket進行讀操作,recv或read函數返回0
- 在監聽的socket上有新的連接請求
- 在socket尚有未處理的錯誤
- 在socket內核中,發送緩沖區中的可用字節數大于等于低水位標記時,可以無阻塞的寫,并且返回值大于0
- socket的寫操作被關閉時,對一個寫操作被關閉的socket進行寫操作,會觸發SIGPIPE信號
- socket使用非阻塞connect連接成功或失敗時
select()如下:
#include <sys/select.h> int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);參數說明
| readfds: | 需要監聽可讀事件的fd集合 |
| writefds: | 需要監聽可寫事件fd的集合 |
| exceptfds: | 需要監聽異常事件的fd集合 |
| timeout: | 超時時間,即在這個參數設定的時間內檢測這些fd的事件,超過這個時間后,select函數立即返回,這是一個timeval結構體 |
其定義如下:
struct timeval{ long tv_sec; /*秒 */long tv_usec; /*微秒 */ }參數readfds,writefds,exceptfds的類型都是fd_set,這是一個結構體信息
定義如下
//#define __FD_SETSIZE 1024 #define __NFDBITS (8 * (int) sizeof (__fd_mask)) #define __FD_ELT(d) ((d) / __NFDBITS) #define __FD_MASK(d) ((__fd_mask) (1UL << ((d) % __NFDBITS)))/* fd_set for select and pselect. */ typedef struct{/* XPG4.2 requires this member name. Otherwise avoid the namefrom the global namespace. */ #ifdef __USE_XOPEN//typedef long int __fd_mask;__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)->fds_bits) #endif} fd_set;/* 最大數量`fd_set'. */ #define FD_SETSIZE __FD_SETSIZE假設未定義__USE_XOPEN整理一年
typedef struct{ //typedef long int __fd_mask;long int fds_bits[__FD_SETSIZE / __NFDBITS];} fd_set;將一個fd添加到fd_set這個集合中時需要使用FD_SET宏,其定義如下:
void FD_SET(fd, fdsetp)實現如下:
#define FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp)__FD_SET (fd, fdsetp)實現如下:
/* We don't use `memset' because this would require a prototype andthe array isn't too big. */ # define __FD_ZERO(set) \do { \unsigned int __i; \fd_set *__arr = (set); \for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i) \__FDS_BITS (__arr)[__i] = 0; \} while (0)#endif /* GNU CC */#define __FD_SET(d, set) \((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))舉個例子,假設現在fd的值為43,那么在數組下表為0的元素中第43個bit被置為1
再Linux上,向fd_set集合中添加新的fd時,采用位圖法確定位置;在windows中添加fd至fd_set的實現規則依次從數組第0個位置開始向后遞增
也就是說,FD_SET宏本質上是在一個有1024個連續bit的數組的第fd位置置1.
同理,FD_CLR刪除一個fd的原理,也就是將數組的第fd位置置為0
實例;
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <iostream> #include <cstring> #include <sys/time.h> #include <vector> #include <cerrno>//Customize the value representing invalid fd #pragma clang diagnostic push #pragma ide diagnostic ignored "EndlessLoop" #define INVALID_FD -1 int main(int argc,char * argv[]) {//create a listen socketint listenfd = socket(AF_INET,SOCK_STREAM,0);if(listenfd == INVALID_FD){printf("創建監聽socket失敗");return -1;}//init server addrsockaddr_in bindaddr{};bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);bindaddr.sin_port= htons(3000);if(bind(listenfd,(struct sockaddr*) &bindaddr, sizeof(bindaddr)) == -1){printf("綁定socket失敗");close(listenfd);return -1;}//start listenif(listen(listenfd,SOMAXCONN) == -1){printf("監聽失敗!");close(listenfd);return -1;}//Store the client's socket datastd::vector<int> clientfds;int maxfd;while(true){fd_set readset;FD_ZERO(&readset);FD_SET(listenfd,&readset);maxfd = listenfd;unsigned long clientfdslength = clientfds.size();for (int i = 0; i < clientfdslength; ++i){if(clientfds[i] != INVALID_FD){FD_SET(clientfds[i],&readset);if(maxfd<clientfds[i])maxfd = clientfds[i];}}timeval tm{};tm.tv_sec = 1;tm.tv_usec =0;int ret = select(maxfd+1,&readset, nullptr, nullptr,&tm);if(ret == -1){if (errno != EINTR)break;}//time outelse if (ret ==0 ){continue;}else{//event detected on a socketif (FD_ISSET(listenfd,&readset)){sockaddr_in clientaddr{};socklen_t clientaddrlen = sizeof(clientaddr);//accept client connectionint clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientaddrlen);if (clientfd == INVALID_FD){break;}std::cout<<"接受到客戶端連接,fd:"<<clientfd<<std::endl;clientfds.push_back(clientfd);}else{//Assume that the data length sent by the client is not greater than 63char recvbuf[64];unsigned long clientfdslength = clientfds.size();for (int i = 0; i < clientfdslength; ++i){if(clientfds[i] != INVALID_FD && FD_ISSET(clientfds[i],&readset)){memset(recvbuf,0, sizeof(recvbuf));//accept dataint length = recv(clientfds[i],recvbuf,64,0);//recv的返回值等于0,表示客戶端關閉了連接if (length <=0 ){//errorstd::cout<<"error"<<clientfds[i]<<std::endl;close(clientfds[i]);clientfds[i] == INVALID_FD;continue;}std::cout<<"clientfd: "<<clientfds[i]<<", recv data:"<<recvbuf<<std::endl;}}}}}//close all client socketint clientfdslength = clientfds.size();for (int i = 0; i < clientfdslength; ++i){if(clientfds[i] != INVALID_FD){close(clientfds[i]);}}//close socketclose(listenfd);return 0; } #pragma clang diagnostic pop使用nc -v 127.0.0.1 3000來模擬客戶端,打開三個終端
關于以上代碼,需要注意以下幾點:
select函數在調用前后可能會修改readfds,writefds,exceptfds所以想在下次調用select函數時服用這些fd_set變量需要重新清零,添加內容
for (int i = 0; i < clientfdslength; ++i){if(clientfds[i] != INVALID_FD){FD_SET(clientfds[i],&readset);if(maxfd<clientfds[i])maxfd = clientfds[i];}}select函數也會修改timeval結構體的值,如果想復用這些變量,需要重新設置
timeval tm{};tm.tv_sec = 1;tm.tv_usec =0;如果將select的timeval參數設置為NULL,則select函數會一直阻塞下去
windows上的socket函數
在windows上,select函數結束后,不會修改timeval函數
TCP網絡編程的基本流程
Linux與C++11多線程編程(學習筆記)
Linux select函數用法和原理
socket的阻塞模式和非阻塞模式(send和recv函數在阻塞和非阻塞模式下的表現)
connect函數在阻塞和非阻塞模式下的行為
獲取socket對應的接收緩沖區中的可讀數據量
總結
以上是生活随笔為你收集整理的Linux select函数用法和原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 盘点程序员最喜欢的15个网站
- 下一篇: 【Linux内核】虚拟地址空间布局架构