linux平臺IO多路複用 select介面使用例子
阿新 • • 發佈:2018-12-12
這幾天在學習net-snmp原始碼,裡面封裝了很多select函式呼叫,這裡記錄一下linux上select的用法以及相關介面。
先看介面:
//標頭檔案 #include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> /* * 引數nfds表示監聽的描述符個數,通常等於最大的描述符加一,select最多同時監聽描述符 * 數量有個上限,FD_SETSIZE(1024),不同平臺這個值可能不同,所以如果程式中監聽數量特別 * 多的話,建議使用epoll。 * * 引數 readfds, writefds, exceptfds表示描述符集,可以把我們關心的描述符放到對應的 * 描述符數組裡面,這三個分別對應著可讀、可寫和異常事件。可以都設定為NULL,這時候select * 呼叫就相當於一個更精確的sleep。 * * 引數 timeout表示select超時時間,如果為NULL的話,表示永久阻塞,除非監聽的描述符集上 * 有事件發生或者收到訊號,為0的話,表示立即返回,其它的值則表示相應的等待時間。 * * 成功返回準備好讀寫的檔案描述符數量, * 返回0表示超時,返回-1表示出錯。 */ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); /* 從fdset中清空該檔案描述符標誌位 */ void FD_CLR(int fd, fd_set *set); /* 判斷該檔案描述符上是否有事件發生 */ int FD_ISSET(int fd, fd_set *set); /* 將該檔案描述符新增到fd_set陣列中 */ void FD_SET(int fd, fd_set *set); /* 初始化fdset */ void FD_ZERO(fd_set *set); 每次呼叫select後,都需要重新清空描述符集並重新新增感興趣的檔案描述符。另外,select返回時會將 剩餘時間填充到timeout引數中,因此重新呼叫select的時候也要重新初始化該時間引數。
示例,建立兩個udp套接字,使用select迴圈監聽可讀事件,注意收到事件處理完成後需要重新對fd_set描述符集進行初始化,
這一點不如epoll使用方便。
/** * Description : linux 環境 select介面使用示例 * 建立兩個udp套接字,然後使用select監聽套接字上讀事件。 * Date : 20181001 * Author : mason */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <sys/errno.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <string.h> #define BUFFER_SIZE 512 #define log(fmt, arg...) printf("[udptest] %s:%d "fmt, __FUNCTION__, __LINE__, ##arg) void main() { int sock, sock2; int addr_len, recv_len; char buffer[BUFFER_SIZE] = {0}; struct sockaddr_in addr, addr2; fd_set rfds; struct timeval tv; int retval, maxfdp1 = 0; /* 建立UDP套接字 */ sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { log("create socket fail \r\n"); return ; } sock2 = socket(AF_INET, SOCK_DGRAM, 0); if (sock2 == -1) { log("create socket2 fail \r\n"); close(sock); return ; } /* 設定監聽地址 */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(40000); addr2.sin_family = AF_INET; addr2.sin_addr.s_addr = INADDR_ANY; addr2.sin_port = htons(30000); addr_len = sizeof(struct sockaddr_in); /* 繫結本地監聽地址 */ if (0 != bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in))) { log("bind local listening addr fail,errno : %d \r\n", errno); goto end; } if (0 != bind(sock2, (struct sockaddr *)&addr2, sizeof(struct sockaddr_in))) { log("bind local listening addr fail,errno : %d \r\n", errno); goto end; } /* 初始化描述符集 */ FD_ZERO(&rfds); /* 新增到描述符集裡面 */ FD_SET(sock, &rfds); maxfdp1 = maxfdp1 > sock ? (maxfdp1 + 1) : (sock + 1); /* 新增到描述符集裡面 */ FD_SET(sock2, &rfds); maxfdp1 = maxfdp1 > sock ? (maxfdp1 + 1) : (sock + 1); /* select超時10s */ tv.tv_sec = 10; tv.tv_usec = 0; /* 迴圈監聽 */ for (;;) { /* 只監聽讀事件 */ retval = select(maxfdp1, &rfds, NULL, NULL, &tv); if (retval > 0) { /* 判斷是否可讀 */ if (FD_ISSET(sock, &rfds)) { recv_len = read(sock, buffer, sizeof(buffer)); if (recv_len != -1) { log("revc from sock : %s\r\n", buffer); memset(buffer, 0, sizeof(buffer)); } } if (FD_ISSET(sock2, &rfds)) { recv_len = read(sock2, buffer, sizeof(buffer)); if (recv_len != -1) { log("revc from sock2 : %s\r\n", buffer); memset(buffer, 0, sizeof(buffer)); } } } else if (retval == 0) { /* select 超時 */ log("select timeout \r\n"); } else { log("select error \r\n"); } /* 清空標誌位 */ FD_ZERO(&rfds); /* 重新設定超時 */ tv.tv_sec = 5; /* 重新新增到select監聽陣列中 */ FD_SET(sock, &rfds); FD_SET(sock2, &rfds); } end: close(sock); close(sock2); return; }
Makefile:
#
# Linux 同步IO複用 select介面例子
#
app:
gcc -o select_demo select_demo.c
clean:
rm -rf *.o select_demo
執行截圖:
參考資料:
2. 《UNIX網路程式設計卷一 套接字API》第6章 IO多路複用