C語言詳解select函數(shù)的使用
select
select API介紹
主旨思想:
- 首先要構(gòu)造一個關(guān)于文件描述符的列表,將要監(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位, 每一個標志位對應(yīng)一個文件描述符
#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)核檢測哪些文件描述符的讀的屬性
- 一般檢測讀操作
- 對應(yīng)的是對方發(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ū)滿了將對應(yīng)文件描述符標志位置為0,有空余的數(shù)據(jù)可以寫, 置為1。
- exceptfds : 檢測發(fā)生異常的文件描述符的集合
- timeout : 設(shè)置的超時時間
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
- NULL : 永久阻塞,直到檢測到了文件描述符有變化
- tv_sec = 0 tv_usec = 0, 不阻塞
- tv_sec > 0 tv_usec > 0, 阻塞對應(yīng)的時間
- 返回值
- -1 : 失敗
- 0 : select函數(shù)中設(shè)置了超時時間,超時時間到了,沒有檢測到,返回0
- >0(n) : 檢測的集合中有n個文件描述符發(fā)生了變化
// 將參數(shù)文件描述符fd對應(yīng)的標志位設(shè)置為0
void FD_CLR(int fd, fd_set *set);// 判斷fd對應(yīng)的標志位是0還是1, 返回值 : fd對應(yīng)的標志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);// 將參數(shù)文件描述符fd 對應(yīng)的標志位,設(shè)置為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)核幫我們檢測),同時假設(shè)A,B發(fā)送了數(shù)據(jù);
A,B對應(yīng)的文件描述符為3和4;

結(jié)果: 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);
// 連接服務(wù)器
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("服務(wù)器已經(jīng)斷開連接...\n");
break;
}
// sleep(1);
usleep(1000);
}
close(fd);
return 0;
}//服務(wù)端
#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)?,因此,我們需要復(fù)制一份。
// 調(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,因為設(shè)置了永久阻塞NULL,直到檢測到文件描述符有數(shù)據(jù)變化
continue;
} else if(ret > 0) { //ret只會返回文件描述符發(fā)生變化的個數(shù),不知道具體哪個發(fā)生了變化,需要遍歷查找
// 說明檢測到了有文件描述符的對應(yīng)的緩沖區(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)) {
// 說明這個文件描述符對應(yīng)的客戶端發(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); //關(guān)閉文件描述符
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;
}編譯運行


再打開一個客戶端:

服務(wù)端可以看出有新客戶端進來了:

又連進來了新的客戶端:

select和poll缺點

到此這篇關(guān)于C語言詳解select函數(shù)的使用的文章就介紹到這了,更多相關(guān)C語言select函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vscode配置C/C++環(huán)境使用minGW(保姆級配置過程)
本文主要介紹了Vscode配置C/C++環(huán)境使用minGW(保姆級配置過程),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
linux下c語言中隱藏進程命令行參數(shù)(例如輸入密碼等高危操作)
啟動程序很多時候用命令行參數(shù)可以很方便,做到簡化一些配置,但是輸入用戶名密碼等操作,如果通過進程查看工具直接看到密碼就太不安全了,這里就為大家分享一下方法2021-01-01
C語言實現(xiàn)圖書管理系統(tǒng)課程設(shè)計
這篇文章主要為大家詳細介紹了C語言實現(xiàn)圖書管理系統(tǒng)課程設(shè)計,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07

