C語言詳解select函數(shù)的使用
select
select API介紹
主旨思想:
- 首先要構造一個關于文件描述符的列表,將要監(jiān)聽的文件描述符添加到該列表中。
- 調(diào)用一個系統(tǒng)函數(shù),監(jiān)聽該列表中的文件描述符,直到這些描述符中的一個或者多個進行I/O操作時,該函數(shù)才返回。
a. 這個函數(shù)是阻塞
b. 函數(shù)對文件描述符的檢測的操作是由內(nèi)核完成的
- 在返回時,它會告訴進程有多少(哪些)描述符要進行I/O操作。
// sizeof(fd_set) = 128字節(jié) 1024位, 每一個標志位對應一個文件描述符
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
- 參數(shù):
- nfds : 委托內(nèi)核檢測的最大文件描述符的值 + 1
- readfds : 要檢測的文件描述符的讀的集合(有數(shù)據(jù)發(fā)送過來),委托內(nèi)核檢測哪些文件描述符的讀的屬性
- 一般檢測讀操作
- 對應的是對方發(fā)送過來的數(shù)據(jù),因為讀是被動的接收數(shù)據(jù),檢測的就是讀緩沖區(qū)
- 是一個傳入傳出參數(shù)(內(nèi)核進行對文件描述符標志位檢測,檢測完后再返回回來;)
檢測過程: 文件描述符在用戶態(tài),1表示文件描述符需要檢測,0表示不需要檢測;在內(nèi)核中處理時:只檢測文件描述符為 1的文件描述符,如果數(shù)據(jù)發(fā)生變化,置為1,不變化置為0,然后返回給用戶態(tài)。
- writefds : 要檢測的文件描述符的寫的集合,委托內(nèi)核檢測哪些文件描述符的寫的屬性
- 委托內(nèi)核檢測寫緩沖區(qū)是不是還可以寫數(shù)據(jù)(不滿的就可以寫)
要檢測哪個文件描述符,就將那個標志位置為1;
緩沖區(qū)滿了將對應文件描述符標志位置為0,有空余的數(shù)據(jù)可以寫, 置為1。
- exceptfds : 檢測發(fā)生異常的文件描述符的集合
- timeout : 設置的超時時間
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
- NULL : 永久阻塞,直到檢測到了文件描述符有變化
- tv_sec = 0 tv_usec = 0, 不阻塞
- tv_sec > 0 tv_usec > 0, 阻塞對應的時間
- 返回值
- -1 : 失敗
- 0 : select函數(shù)中設置了超時時間,超時時間到了,沒有檢測到,返回0
- >0(n) : 檢測的集合中有n個文件描述符發(fā)生了變化
// 將參數(shù)文件描述符fd對應的標志位設置為0
void FD_CLR(int fd, fd_set *set);// 判斷fd對應的標志位是0還是1, 返回值 : fd對應的標志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);// 將參數(shù)文件描述符fd 對應的標志位,設置為1
void FD_SET(int fd, fd_set *set);// fd_set一共有1024 bit位, 全部初始化為0
void FD_ZERO(fd_set *set);
先創(chuàng)建檢測讀的文件描述符集合
fd_set reads;
將3,4,100,101四個文件描述符置為1,表示需要對這些文件描述符進行檢測;
接下來調(diào)用select函數(shù):
select(101+1, &reads, NULL, NULL, NULL); //第一個參數(shù),需要檢測的文件描述符+1 --(+1才能遍歷到101,因為是從0開始的)
將fd_set從內(nèi)核態(tài)拷貝到用戶態(tài)(由內(nèi)核幫我們檢測),同時假設A,B發(fā)送了數(shù)據(jù);
A,B對應的文件描述符為3和4;
結果: 3和4有數(shù)據(jù),置為1,;100和101沒有數(shù)據(jù),將其1置為0。
修改完之后,再將fd_set從內(nèi)核態(tài)拷到用戶態(tài);
用戶可以遍歷這個集合,找到哪個文件描述符為1,即3和4,就說明有數(shù)據(jù)了。
select 代碼
//客戶端 #include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { // 創(chuàng)建socket int fd = socket(PF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); return -1; } struct sockaddr_in seraddr; inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(9999); // 連接服務器 int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr)); if(ret == -1){ perror("connect"); return -1; } int num = 0; while(1) { char sendBuf[1024] = {0}; sprintf(sendBuf, "send data %d", num++); write(fd, sendBuf, strlen(sendBuf) + 1); // 接收 int len = read(fd, sendBuf, sizeof(sendBuf)); if(len == -1) { perror("read"); return -1; }else if(len > 0) { printf("read buf = %s\n", sendBuf); } else { printf("服務器已經(jīng)斷開連接...\n"); break; } // sleep(1); usleep(1000); } close(fd); return 0; }
//服務端 #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> int main() { // 創(chuàng)建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr; saddr.sin_port = htons(9999); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; // 綁定 bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); // 監(jiān)聽 listen(lfd, 8); // 創(chuàng)建一個fd_set的集合,存放的是需要檢測的文件描述符 fd_set rdset, tmp; //fd_set底層可以表示1024個文件描述符 FD_ZERO(&rdset); //初始化 FD_SET(lfd, &rdset); //添加需要監(jiān)聽的文件描述符 int maxfd = lfd; //定義最大文件描述符,作為參數(shù)傳入select函數(shù)中 while(1) { tmp = rdset; //rdset這個不能變,因為內(nèi)核再檢測時,如果沒有數(shù)據(jù),就會將其變?yōu)?,因此,我們需要復制一份。 // 調(diào)用select系統(tǒng)函數(shù),讓內(nèi)核幫檢測哪些文件描述符有數(shù)據(jù) int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL); if(ret == -1) { perror("select"); exit(-1); } else if(ret == 0) { //這里不可能為0,因為設置了永久阻塞NULL,直到檢測到文件描述符有數(shù)據(jù)變化 continue; } else if(ret > 0) { //ret只會返回文件描述符發(fā)生變化的個數(shù),不知道具體哪個發(fā)生了變化,需要遍歷查找 // 說明檢測到了有文件描述符的對應的緩沖區(qū)的數(shù)據(jù)發(fā)生了改變 if(FD_ISSET(lfd, &tmp)) { //lfd為監(jiān)聽文件描述符 // 表示有新的客戶端連接進來了 struct sockaddr_in cliaddr; int len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); // 將新的文件描述符加入到集合中,下一次select檢測時,需要檢測這些通信的文件描述符有沒有數(shù)據(jù) FD_SET(cfd, &rdset); // 更新最大的文件描述符 maxfd = maxfd > cfd ? maxfd : cfd; } //檢測剩余文件描述符有沒有數(shù)據(jù)變化,從lfd+1開始即可 for(int i = lfd + 1; i <= maxfd; i++) { if(FD_ISSET(i, &tmp)) { // 說明這個文件描述符對應的客戶端發(fā)來了數(shù)據(jù) char buf[1024] = {0}; int len = read(i, buf, sizeof(buf)); if(len == -1) { perror("read"); exit(-1); } else if(len == 0) { //說明客戶端斷開連接 printf("client closed...\n"); close(i); //關閉文件描述符 FD_CLR(i, &rdset); //fd_set中不在監(jiān)測這個文件描述符 } else if(len > 0) { printf("read buf = %s\n", buf); write(i, buf, strlen(buf) + 1); } } } } } close(lfd); return 0; }
編譯運行
再打開一個客戶端:
服務端可以看出有新客戶端進來了:
又連進來了新的客戶端:
select和poll缺點
到此這篇關于C語言詳解select函數(shù)的使用的文章就介紹到這了,更多相關C語言select函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++實現(xiàn)LeetCode(137.單獨的數(shù)字之二)
這篇文章主要介紹了C++實現(xiàn)LeetCode(137.單獨的數(shù)字之二),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07詳解vs2022創(chuàng)建及調(diào)用.lib的方法
這篇文章主要介紹了vs2022創(chuàng)建及調(diào)用.lib的方法,調(diào)用Lib的原則就是可以讓編譯器找到頭文件和庫文件的目錄,并正確引入,本文給大家詳細講解需要的朋友可以參考下2022-11-11