Linux I/O復用之select函數詳解

http://blog.csdn.net/y396397735/article/details/55004775

select函數的功能和調用順序

使用select函數時統一監視多個文件描述符的:?
1、 是否存在套接字接收數據??
2、 無需阻塞傳輸數據的套接字有哪些??
3、 哪些套接字發生了異常?


select函數調用過程:?
這里寫圖片描述?
由上圖知,調用select函數需要一些準備工作,調用后還需要查看結果。


設置文件描述符

select可以同時監視多個文件描述符(套接字)。?
此時需要先將文件描述符集中到一起。集中時也要按照監視項(接收,傳輸,異常)進行區分,即按照上述3種監視項分成三類。?
使用fd_set數組變量執行此項操作,該數組是存有0和1的位數組。

這里寫圖片描述

最左端的位表示文件描述符0(位置)。如果該位值為1,則表示該文件描述符是監視對象。?
圖上顯然監視對象為fd1和fd3。


“是否應當通過文件描述符的數字直接將值注冊到fd_set變量?”?
當然不是!操作fd_set的值由如下宏來完成:

FD_ZERO(fd_set* fdset): 將fd_set變量的所有位初始化為0。?
FD_SET(int fd, fd_set* fdset):在參數fdset指向的變量中注冊文件描述符fd的信息。?
FD_CLR(int fd, fd_set* fdset):參數fdset指向的變量中清除文件描述符fd的信息。?
FD_ISSET(int fd, fd_set* fdset):?若參數fdset指向的變量中包含文件描述符fd的信息,則返回真。

畫圖解釋:?
這里寫圖片描述


設置監視范圍及超時

select函數:

#include <sys/select.h>
#include <sys/time.h>int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

其中參數和返回值:?
maxfd:監視對象文件描述符數量。?
readset: 將所有關注“是否存在待讀取數據”的文件描述符注冊到fd_set變量,并傳遞其地址值。?
writeset: 將所有關注“是否可傳輸無阻塞數據”的文件描述符注冊到fd_set變量,并傳遞其地址值。?
exceptset: 將所有關注“是否發生異常”的文件描述符注冊到fd_set變量,并傳遞其地址值。?
timeout: 調用select后,為防止陷入無限阻塞狀態,傳遞超時信息。?
返回值:錯誤返回-1,超時返回0。因關注的事件返回時,返回大于0的值,該值是發生事件的文件描述符數。


select函數用來驗證3種監視項的變化情況。根據監視項聲明3個fd_set變量,分別向其注冊文件描述符信息,并把變量的地址傳遞到函數的第二到第四個參數。但是,在調用select函數前需要決定2件事:?
“文件描述符的監視范圍是?”?
“如何設定select函數的超時時間?”

第一,文件描述符的監視范圍與第一個參數有關,實際上,select函數要求通過第一個參數傳遞監視對象文件描述符的數量。因此,需要得到注冊在fd_set變量中的文件描述符數。但每次新建文件描述符時,其值都會增1,故只需將最大的文件描述符值加1再傳遞到select函數即可。(加1是因為文件描述符的值從0開始)?
第二,超時時間與 最后一個參數有關,其中timeval結構體如下:

struct timeval
{long tv_sec;long tv_usec;
};
  • 1
  • 2
  • 3
  • 4
  • 5

本來select函數只有在監視文件描述符發生變化時才返回,未發生變化會進入阻塞狀態。指定超時時間就是為了防止這種情況發生。?
將上述結構體填入時間值,然后將結構體地址值傳給select函數的最后一個參數,此時,即使文件描述符中未發生變化,只要過了指定時間,也可以從函數返回。不過這種情況下,select函數返回0。?
不想設置超時最后一個參數只需要傳遞NULL。


調用select函數后查看結果

如果select返回值大于0,說明文件描述符發生了變化。

關于文件描述符變化:文件描述符變化是指監視的文件描述符中發生了相應的監視事件。例如通過select的第二個參數傳遞的集合中存在需要讀取數據的描述符時,就意味著文件描述符發生變化。
  • 1
  • 2
  • 3

怎樣獲知哪些文件描述符發生了變化?向select函數的第二到第四個參數傳遞的fd_set變量中將產生變化,如下圖:

這里寫圖片描述

select函數調用完成后,向其傳遞的fd_set變量中將發生變化。原來為1的所有位均變為0,但發生變化的文件描述符對應位除外。因此,可以認為值為1的位置上的文件描述符發生了變化


select函數調用實例

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>#define BUF_SIZE 30int main(int argc, char* argv[])
{fd_set reads, temps;int result, str_len;char buf[BUF_SIZE];struct timeval timeout;FD_ZERO(&reads);FD_SET(0, &reads);//監視文件描述符0的變化, 即標準輸入的變化/*超時不能在此設置!因為調用select后,結構體timeval的成員tv_sec和tv_usec的值將被替換為超時前剩余時間.調用select函數前,每次都需要初始化timeval結構體變量.timeout.tv_sec = 5;timeout.tv_usec = 5000;*/while(1){/*將準備好的fd_set變量reads的內容復制到temps變量,因為調用select函數后,除了發生變化的fd對應位外,剩下的所有位都將初始化為0,為了記住初始值,必須經過這種復制過程。*/temps = reads;//設置超時timeout.tv_sec = 5;timeout.tv_usec = 0;//調用select函數. 若有控制臺輸入數據,則返回大于0的整數,如果沒有輸入數據而引發超時,返回0.result = select(1, &temps, 0, 0, &timeout);if(result == -1){perror("select() error");break;}else if(result == 0){puts("timeout");}else{//讀取數據并輸出if(FD_ISSET(0, &temps)){str_len = read(0, buf, BUF_SIZE);buf[str_len] = 0;printf("message from console: %s", buf);}}}return 0;
}程序運行結果:
/*
nihao
message from console: nihao
goodbye
message from console: goodbye
timeout
timeout
*/

select函數實現I/O復用服務端

服務端代碼:

//server.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/select.h>#define BUF_SIZE 100void error_handing(char* buf);int main(int argc, char* argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;struct timeval timeout;fd_set reads, cpy_reads;socklen_t adr_sz;int fd_max, str_len, fd_num, i;char buf[BUF_SIZE];if(argc != 2){printf("Usage: %s <port> \n", argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)error_handing("bind() error");if(listen(serv_sock, 5) == -1)error_handing("listen() error");FD_ZERO(&reads);FD_SET(serv_sock, &reads); //將服務端套接字注冊入fd_set,即添加了服務器端套接字為監視對象fd_max = serv_sock;while(1){cpy_reads = reads;timeout.tv_sec = 5;timeout.tv_usec = 5000;//無限循環調用select 監視可讀事件if((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout)) == -1){perror("select error");break;}if(fd_num == 0)continue;for(i = 0; i < fd_max + 1; i++){if(FD_ISSET(i, &cpy_reads)){/*發生狀態變化時,首先驗證服務器端套接字中是否有變化.①若是服務端套接字變化,接受連接請求。②若是新客戶端連接,注冊與客戶端連接的套接字文件描述符.*/if(i == serv_sock){adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);FD_SET(clnt_sock, &reads);if(fd_max < clnt_sock)fd_max = clnt_sock;printf("connected client: %d \n", clnt_sock);}else    {str_len = read(i, buf, BUF_SIZE);if(str_len == 0)    //讀取數據完畢關閉套接字{FD_CLR(i, &reads);//從reads中刪除相關信息close(i);printf("closed client: %d \n", i);}else{write(i, buf, str_len);//執行回聲服務  即echo}}}}}close(serv_sock);return 0;
}void error_handing(char* buf)
{fputs(buf, stderr);fputc('\n', stderr);exit(1);
}

客戶端代碼:

//client.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#define BUF_SIZE 1024void error_handling(char* message);int main(int argc, char* argv[])
{int sock;char message[BUF_SIZE];int str_len;struct sockaddr_in serv_adr;if(argc != 3){printf("Usage: %s <IP> <port> \n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);if(sock==-1)error_handling("socket error");memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)error_handling("connect() error!");elseputs("connected....");while(1){fputs("Input message:(輸入Q退出):", stdout);fgets(message, BUF_SIZE, stdin);if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))break;write(sock, message, strlen(message));str_len = read(sock, message, BUF_SIZE-1);message[str_len] = 0;printf("Message from server: %s", message);}close(sock);return 0;
}void error_handling(char* message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

編譯執行測試:

編譯程序:
gcc server.c –o server
gcc client.c –o client啟動服務端:
yu@ubuntu:~/qtProjects/echo_selectserv$ ./server 8899啟動客戶端1:
yu@ubuntu:~/qtProjects/echo_selectserv$ ./client 127.0.0.1 8899
connected....
Input message:(輸入Q退出):你好哇201721220:25:43
Message from server: 你好哇201721220:25:43
Input message:(輸入Q退出):你好哇2017-02-12 20:25:52
Message from server: 你好哇2017-02-12 20:25:52
Input message:(輸入Q退出):q啟動客戶端2:
yu@ubuntu:~/qtProjects/echo_selectserv$ ./client 127.0.0.1 8899
connected....
Input message:(輸入Q退出):你好201721220:25:11
Message from server: 你好201721220:25:11
Input message:(輸入Q退出):你好201721220:25:24
Message from server: 你好201721220:25:24
Input message:(輸入Q退出):q服務端情況:
yu@ubuntu:~/qtProjects/echo_selectserv$ ./server 8899
connected client: 4 
connected client: 5 
closed client: 5 
closed client: 4

學習自《TCP/IP網絡編程》



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383847.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383847.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383847.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Java學習筆記一】類和對象

面向對象程序設計的一個一個重要特點是&#xff1a;封裝性。 這里的封裝性有兩方面含義&#xff1a;一是將有關的數據和操作代碼封裝在一個對象中形成一個基本單位&#xff0c;各個對象之間相互獨立互不干擾&#xff0c;二是將對象中某些部分對外隱蔽&#xff0c;即隱蔽其內部細…

深入研究socket編程(3)——使用select函數編寫客戶端和服務器

http://blog.csdn.net/chenxun_2010/article/details/50488394 首先看原先《UNIX網絡編程——并發服務器&#xff08;TCP&#xff09;》的代碼&#xff0c;服務器代碼serv.c&#xff1a; [cpp] view plaincopy #include<stdio.h> #include<sys/types.h> #inclu…

Java簡單輸入輸出

不同于面向過程中有直接的輸入輸出函數&#xff0c;Java中的輸入輸出只能通過類來實現。 比較常見的一種是使用Scanner類 需要引入java.util包&#xff0c;即在文件開始加上語句import java.util.*;創建Scanner類對象&#xff0c;屬于標準輸入流。 例如Scanner snew Scanner(S…

Ubuntu安裝搭建Clion環境

嗚嗚嗚&#xff0c;太辛苦了&#xff0c;我終于安裝好這個了。 大概過程就是先在官網下載安裝包&#xff0c;然后解壓以后用終端移動到對應文件夾下運行clin.sh 運行完以后會有一些窗口&#xff0c;第一個選擇don’t~~&#xff0c;然后點擊ok 接受&#xff08;你可以不接受…

UNIX網絡編程——select函數的并發限制和 poll 函數應用舉例

http://blog.csdn.net/chenxun_2010/article/details/50489577 一、用select實現的并發服務器&#xff0c;能達到的并發數&#xff0c;受兩方面限制 1、一個進程能打開的最大文件描述符限制。這可以通過調整內核參數。可以通過ulimit -n來調整或者使用setrlimit函數設置&#x…

【Java學習筆記二】繼承和多態

與C不同的是&#xff0c;在Java中&#xff0c;一個類只能直接繼承另一個類&#xff0c;而不允許繼承多個類&#xff0c;這個新類稱為繼承類、派生類或者子類&#xff0c;而被繼承的類稱為基類或者父類。 繼承類能夠繼承基類的群不屬性和行為。 面向對象程序設計的三大特點為&…

使用poll實現的io多路復用服務端和客戶端

http://blog.csdn.net/robertkun/article/details/52269313 參考&#xff1a;http://www.cnblogs.com/Anker/p/3261006.html 使用poll實現的io多路復用服務端和客戶端。 客戶端通過子進程創建多個客戶端連接。 客戶端每隔1秒向服務端發送一個時間戳&#xff0c; 服務端接收到時…

【Java學習筆記三】抽象類與接口

對象的類型轉換分為自動轉換和強制轉換兩種 派生類向基類轉換是自動轉換&#xff0c;因為派生類中包含基類基類向派生類的轉換是強制轉換 強制類型轉換是通過在轉換對象前面使用圓括號運算符來實現&#xff0c;圓括號內為要轉換的目標類型&#xff0c;格式為&#xff1a; (&…

Epoll 的tcp通信代碼(服務器+客戶端)

http://blog.csdn.net/libinbin_1014/article/details/50096187 Epoll 的tcp通信代碼&#xff08;服務器客戶端&#xff09; /* gcc -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS64 -I${ORACLE_HOME}/rdbms/public -I${ORACLE_HOME}/rdbms/demo -L${ORACLE_HOME}/lib -lclntsh …

【Java學習筆記四】Java中的包

包的聲明和引入&#xff1a;在Java語言系統中&#xff0c;Java編譯器為每一個類生成一個字節碼文件&#xff08;.class&#xff09;&#xff0c;為了對類文件進行分層和按用途分類管理&#xff0c;同時也為了解決相同類名的文件沖突的問題&#xff0c;Java提供了包機制來管理類…

Linux系統編程——線程池

http://blog.csdn.net/tennysonsky/article/details/46490099# 線程池基本原理 在傳統服務器結構中&#xff0c;常是有一個總的監聽線程監聽有沒有新的用戶連接服務器&#xff0c;每當有一個新的用戶進入&#xff0c;服務器就開啟一個新的線程用戶處理這 個用戶的數據包。這個線…

【Java學習筆記五】Java異常處理

異常通常分為三類&#xff1a; 程序可控制的異常&#xff1a;一般是可預見的錯誤&#xff0c;不是致命的。例如&#xff1a;除數為0&#xff0c;數組下標越界。程序不可控制的的異常&#xff1a;這種異常往往是致命的&#xff0c;但是系統可以預見的。例如&#xff1a;系統棧溢…

【C++學習筆記一】C++類和對象詳解

類定義是以關鍵字class開頭&#xff0c;后面跟類的名稱。主體是包含在一對花括號中。類定義后必須跟著一個分號或一個聲明列表。 類的對象的公共數據成員可以使用直接成員訪問運算符.來訪問。需要注意的是&#xff0c;私有的成員和受保護的成員不能直接使用成員訪問運算符來訪…

C語言實現的簡單的線程池

http://www.linuxidc.com/Linux/2013-01/77619.htm 有時我們會需要大量線程來處理一些相互獨立的任務&#xff0c;為了避免頻繁的申請釋放線程所帶來的開銷&#xff0c;我們可以使用線程池。下面是一個C語言實現的簡單的線程池。 頭文件&#xff1a; 1: #ifndef THREAD_POOL_H_…

C++獲取當前時間

可以使用windowsAPI直接獲取。 例如&#xff1a; #include<windows.h> #include<cstdio>using namespace std;int main() {SYSTEMTIME now;GetLocalTime(&now);printf("現在是%02d時%02d分%02d秒\n",now.wHour,now.wMinute,now.wSecond);printf(&…

成員函數后面加上const的作用

const表示成員函數不會修改類中的數據成員。 規則&#xff1a; 在類中被const 聲明的函數只能訪問const 函數&#xff0c;而非const 函數可以訪問任意成員函數。在成員函數中不管數據是否具有const 屬性&#xff0c;編譯器檢查的的是是否有修改&#xff08;賦值&#xff0c;自…

簡單Linux C線程池

http://www.cnblogs.com/venow/archive/2012/11/22/2779667.html 大多數的網絡服務器&#xff0c;包括Web服務器都具有一個特點&#xff0c;就是單位時間內必須處理數目巨大的連接請求&#xff0c;但是處理時間卻是比較短的。在傳統的多線程服務器模型中是這樣實現的&#xff1…

C++創建對象:棧和堆的區別

首先我們應該了解棧和堆的差別&#xff1a; 詳細信息&#xff1a;傳送門 棧相當于函數自帶的存儲空間&#xff0c;在windows下一般為2M,在Linux下一般為8M&#xff0c;存取速度稍微快一點。堆是系統的空間&#xff0c;相對較大&#xff0c;一般為2G&#xff0c;效率稍微慢一點…

IO多路復用之poll總結

http://www.cnblogs.com/Anker/p/3261006.html 1、基本知識 poll的機制與select類似&#xff0c;與select在本質上沒有多大差別&#xff0c;管理多個描述符也是進行輪詢&#xff0c;根據描述符的狀態進行處理&#xff0c;但是poll沒有最大文件描述符數量的限制。poll和select同…

【C++學習筆記二】C++繼承

繼承 繼承允許我們一句另一個類來定義一個類&#xff0c;這使得繼承和維護一個程序變得更加容易&#xff0c;也達到了重用代碼功能和提高執行效率的效果。 一般格式為&#xff1a; class 派生類名 :訪問修飾符 基類名{};其中訪問修飾符是public protected private中的一個&a…