C語言詳解select函數(shù)的使用
select
select API介紹
主旨思想:
- 首先要構(gòu)造一個(gè)關(guān)于文件描述符的列表,將要監(jiān)聽的文件描述符添加到該列表中。
- 調(diào)用一個(gè)系統(tǒng)函數(shù),監(jiān)聽該列表中的文件描述符,直到這些描述符中的一個(gè)或者多個(gè)進(jìn)行I/O操作時(shí),該函數(shù)才返回。
a. 這個(gè)函數(shù)是阻塞
b. 函數(shù)對文件描述符的檢測的操作是由內(nèi)核完成的
- 在返回時(shí),它會告訴進(jìn)程有多少(哪些)描述符要進(jìn)行I/O操作。
// sizeof(fd_set) = 128字節(jié) 1024位, 每一個(gè)標(biāo)志位對應(yīng)一個(gè)文件描述符
#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ù),因?yàn)樽x是被動(dòng)的接收數(shù)據(jù),檢測的就是讀緩沖區(qū)
- 是一個(gè)傳入傳出參數(shù)(內(nèi)核進(jìn)行對文件描述符標(biāo)志位檢測,檢測完后再返回回來;)
檢測過程: 文件描述符在用戶態(tài),1表示文件描述符需要檢測,0表示不需要檢測;在內(nèi)核中處理時(shí):只檢測文件描述符為 1的文件描述符,如果數(shù)據(jù)發(fā)生變化,置為1,不變化置為0,然后返回給用戶態(tài)。
- writefds : 要檢測的文件描述符的寫的集合,委托內(nèi)核檢測哪些文件描述符的寫的屬性
- 委托內(nèi)核檢測寫緩沖區(qū)是不是還可以寫數(shù)據(jù)(不滿的就可以寫)
要檢測哪個(gè)文件描述符,就將那個(gè)標(biāo)志位置為1;
緩沖區(qū)滿了將對應(yīng)文件描述符標(biāo)志位置為0,有空余的數(shù)據(jù)可以寫, 置為1。
- exceptfds : 檢測發(fā)生異常的文件描述符的集合
- timeout : 設(shè)置的超時(shí)時(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)的時(shí)間
- 返回值
- -1 : 失敗
- 0 : select函數(shù)中設(shè)置了超時(shí)時(shí)間,超時(shí)時(shí)間到了,沒有檢測到,返回0
- >0(n) : 檢測的集合中有n個(gè)文件描述符發(fā)生了變化
// 將參數(shù)文件描述符fd對應(yīng)的標(biāo)志位設(shè)置為0
void FD_CLR(int fd, fd_set *set);// 判斷fd對應(yīng)的標(biāo)志位是0還是1, 返回值 : fd對應(yīng)的標(biāo)志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);// 將參數(shù)文件描述符fd 對應(yīng)的標(biāo)志位,設(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四個(gè)文件描述符置為1,表示需要對這些文件描述符進(jìn)行檢測;
接下來調(diào)用select函數(shù):
select(101+1, &reads, NULL, NULL, NULL); //第一個(gè)參數(shù),需要檢測的文件描述符+1 --(+1才能遍歷到101,因?yàn)槭菑?開始的)
將fd_set從內(nèi)核態(tài)拷貝到用戶態(tài)(由內(nèi)核幫我們檢測),同時(shí)假設(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);
用戶可以遍歷這個(gè)集合,找到哪個(gè)文件描述符為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)建一個(gè)fd_set的集合,存放的是需要檢測的文件描述符 fd_set rdset, tmp; //fd_set底層可以表示1024個(gè)文件描述符 FD_ZERO(&rdset); //初始化 FD_SET(lfd, &rdset); //添加需要監(jiān)聽的文件描述符 int maxfd = lfd; //定義最大文件描述符,作為參數(shù)傳入select函數(shù)中 while(1) { tmp = rdset; //rdset這個(gè)不能變,因?yàn)閮?nèi)核再檢測時(shí),如果沒有數(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,因?yàn)樵O(shè)置了永久阻塞NULL,直到檢測到文件描述符有數(shù)據(jù)變化 continue; } else if(ret > 0) { //ret只會返回文件描述符發(fā)生變化的個(gè)數(shù),不知道具體哪個(gè)發(fā)生了變化,需要遍歷查找 // 說明檢測到了有文件描述符的對應(yīng)的緩沖區(qū)的數(shù)據(jù)發(fā)生了改變 if(FD_ISSET(lfd, &tmp)) { //lfd為監(jiān)聽文件描述符 // 表示有新的客戶端連接進(jìn)來了 struct sockaddr_in cliaddr; int len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); // 將新的文件描述符加入到集合中,下一次select檢測時(shí),需要檢測這些通信的文件描述符有沒有數(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)) { // 說明這個(gè)文件描述符對應(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)測這個(gè)文件描述符 } else if(len > 0) { printf("read buf = %s\n", buf); write(i, buf, strlen(buf) + 1); } } } } } close(lfd); return 0; }
編譯運(yùn)行
再打開一個(gè)客戶端:
服務(wù)端可以看出有新客戶端進(jìn)來了:
又連進(jìn)來了新的客戶端:
select和poll缺點(diǎn)
到此這篇關(guān)于C語言詳解select函數(shù)的使用的文章就介紹到這了,更多相關(guān)C語言select函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c語言實(shí)現(xiàn)通訊錄管理系統(tǒng)詳細(xì)實(shí)例
這篇文章主要給大家介紹了關(guān)于c語言實(shí)現(xiàn)通訊錄管理系統(tǒng)的相關(guān)資料,通訊錄管理系統(tǒng)是一種常見的應(yīng)用程序,可以用來管理聯(lián)系人的信息,包括姓名、電話號碼、地址等,需要的朋友可以參考下2023-07-07C++實(shí)現(xiàn)LeetCode(137.單獨(dú)的數(shù)字之二)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(137.單獨(dú)的數(shù)字之二),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07詳解vs2022創(chuàng)建及調(diào)用.lib的方法
這篇文章主要介紹了vs2022創(chuàng)建及調(diào)用.lib的方法,調(diào)用Lib的原則就是可以讓編譯器找到頭文件和庫文件的目錄,并正確引入,本文給大家詳細(xì)講解需要的朋友可以參考下2022-11-11