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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux select函数用法和原理

發布時間:2023/12/10 linux 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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);

    參數說明

    nfds:Linux上的socket也叫作fd,將這個參數的值設置為所有需要使用select函數檢測事件的fd中的最大值加1即nfds=max(fd1,fd2,...,fdn)+1
    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函数用法和原理的全部內容,希望文章能夠幫你解決所遇到的問題。

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