Unix網絡編程(六)高級I/O技術之復用技術 select

轉載:http://blog.csdn.net/michael_kong_nju/article/details/44887411

I/O復用技術

本文將討論網絡編程中的高級I/O復用技術,將從下面幾個方面進行展開:

a. 什么是復用技術呢?

b. 什么情況下需要使用復用技術呢?

c. I/O的復用技術的工作原理是什么?

d. select, poll and epoll的實現機制,以及他們之間的區別。

下面我們以一個背景問題來開始:

包括在以前的文章中我們討論的案例都是阻塞式的I/O包括(fgetc/getc, fgets/gets),即當輸入條件未滿足時進程會阻塞直到滿足之后進行讀取,但是這樣導致的一個

問題是如果此時進程還有別的I/O信息需要讀取那么這些信息將會被進程忽略掉。如何解決這個問題呢??

可能這么說還是有點抽象,我們針對之前的回射程序舉個例子。程序在:http://blog.csdn.net/michael_kong_nju/article/details/43457393

在正常連接的情況下,客戶端阻塞于fgets函數,此時如果服務器終止我們發現客戶端沒有得到消息,仍然阻塞:



這個時候看下網絡狀態,發現服務器已經結束,而客戶端處于CLOSE_WAIT狀態。


而客戶端此時得不到這個FIN的消息,一直阻塞。而這是我們不希望看到,那么這個問題該怎么解決呢?

可以從下面幾個條件來考慮:

1.使用多進程或者多線程,讓不同的線程或者進程阻塞在不同的描述符上。

但是這種方法會造成程序的復雜,而且進程和線程也需要OS資源的消耗,如果訪問請求過大的話,那么很可能造成服務器的崩潰。Apache服務器是用的子進程的方式,

其中的優點是在于不同的線程服務于不同的用戶可以隔離用戶。


2.用一個進程,但是使用的是非阻塞的I/O讀取數據

當一個I/O不可讀的時候立刻返回,檢查下一個是否可讀,這種形式的循環為輪詢(polling),這種方法比較浪費CPU時間,因為大多數時間是不可讀,但是仍花費時間不斷反復執行read系統調用。


3. 采用信號驅動式的I/O技術

使用這種方法,進程不會阻塞,而是設置一個信號處理函數,當I/O條件滿足時由內核通知進程進行數據讀取。但是這也會有一個問題,如果請求很多的話,那么需要的信號也很多。


4. 使用異步I/O(asynchronous I/O)技術

和信號驅動式類似,異步I/O技術也是使用信號進行通知進程,但是不同的是這里只有一個階段,即當內核完成i/o操作之后會通知進程而不是就緒的時候。


關于2,3,4是另外的幾種高級I/O技術,我們將在后面的文章分別進行詳細的討論。


還有一種方法就是我們即將討論的I/O多路復用技術,下面先回答第一個問題

什么是復用技術呢?


I/O復用技術是一種預先告知內核此進程需要進行哪些I/O,并且當任何指定一個或多個I/O條件就緒時內核通知進程去進行處理的一種技術。他使得一個進程在不阻塞的

情況下處理多個描述符I/O.


針對上面的背景問題,我們可以回答我們開始的第二個疑問,


什么情況下需要使用復用技術呢?


(1)當客戶處理多個描述符時,即上面的這種情況,同時處理交互式輸入和網絡套接字。

(2)當客戶需要處理多個套接字時。

(3)當服務器需要處理多個套接字時,即并發服務器模型。

(4)當服務器需要同時處理TCP和UDP等不同的傳輸協議時也需要使用多路復用技術。


上面大概是幾種需要使用多路復用技術的場景,下面我們來討論復用技術的實現原理。回答開頭的第三個問題:


I/O的復用技術的工作原理是什么?


下面這幅圖是復用技術的工作模型,可以看到這里是使用select來實現的,當然也可以用epoll和poll來實現只是其中的具體細節不一樣罷了。



下面我們開始回答最后一個問題:

select, poll and epoll的實現機制,以及他們之間的區別。


select函數:

該函數準許進程指示內核等待多個事件中的任何一個發送,并只在有一個或多個事件發生或經歷一段指定的時間后才喚醒。函數原型如下:

#include <sys/select.h>
#include <sys/time.h>int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就緒描述符的數目,超時返回0,出錯返回-1

函數參數介紹如下:

(1)第一個參數maxfdp1指定待測試的描述字個數,注意這里是個數是實際的最大的描述符加1,和數組下標類似從0到maxfdp 共maxfd + 1個,所以這里是maxfd plus 1. 描述字0、1、2...maxfdp1-1均將被測試。在linux中,頭文件<sys/select.h>定義了最大的描述符是1024,所以這里最大的maxfdp1也就是1025,在互聯網沒有快速發展的時候這個值可能已經很大了,但是在現在看來很容易就會實現這么大的并發,所以select在很多條件下已經不能滿足服務器的要求了,所以出現了epoll這種無限制的機制。

(2)中間的三個參數readset、writeset和exceptset指定我們要讓內核測試讀、寫和異常條件的描述字。如果對某一個的條件不感興趣,就可以把它設為空指針。struct fd_set可以理解為一個集合,這個集合中存放的是文件描述符,可通過以下四個宏進行設置:

????????? void FD_ZERO(fd_set *fdset);?????????? //清空集合 ? 例如: fd_set rset;?FD_ZERO(&set) ; 初始化,將所有位置0

????????? void FD_SET(int fd, fd_set *fdset);?? //將一個給定的文件描述符加入集合之中 ??FD_SET(1, &rset); 1bit開啟。

????????? void FD_CLR(int fd, fd_set *fdset);?? //將一個給定的文件描述符從集合中刪除

????????? int FD_ISSET(int fd, fd_set *fdset);?? // 檢查集合中指定的文件描述符是否可以讀寫。

在select中集合是使用整數數組實現的,數組中的每一個位都是一個int可以表示32bit,即fd_set[0]可以用來表示0-31號描述符,下面依次。

(3)timeout告知內核等待所指定描述字中的任何一個就緒可花多少時間。其timeval結構用于指定這段時間的秒數和微秒數。

???????? struct timeval{

?????????????????? long tv_sec;?? //seconds

?????????????????? long tv_usec;? //microseconds

?????? };

這個參數有三種可能:

(1)永遠等待下去:僅在有一個描述字準備好I/O時才返回。為此,把該參數設置為空指針NULL。

(2)等待一段固定時間:在有一個描述字準備好I/O時返回,但是不超過由該參數所指向的timeval結構中指定的秒數和微秒數。

(3)根本不等待:檢查描述字后立即返回,這稱為輪詢。為此,該參數必須指向一個timeval結構,而且其中的定時器值必須為0。

下面看看描述有哪些就緒條件;

準備好讀:

1,套接字接收緩沖區的數據字節數大于等于,套接字接收緩沖區低水位線,可以用SO_RCVLOWAT套接選項來設置低水位線,對于TCP和UDP套按字,默認值為1

2,該連接的讀半部分關閉(接收到了FIN的TCP連接).對這樣的套接字讀操作,返回0(EOF)

3,該套接字是一個監聽套接字且已經完成的連接數不為0.對這樣的套按字的accept通常不會阻塞

4,其上有一個套接字錯誤街處理.對這樣的套按字的讀操作將不阻塞并返回-1(錯誤),同時把errno設置成錯誤條件,這些待處理錯誤也可以通過指定SO_ERROR套接字選項調用getsockopt獲取.

準備好寫:

1,該套接字發送緩沖區的可用字節數大于等于套接字發送緩沖區低水位線的當前大小.并且或者該套接已經連接,或者套按字不需要連接(UDP),如果我們把這套接字設置成非阻塞,寫操作將不阻塞并返回一個正值.可以使用SO_SNDLOWAT設置一個該套接字的低水位標記.對于TCP和UDP默認值通常為2048.

2,該連接的寫半部關閉.對這樣的套接寫的寫操作將產生SIGPIPE信號.

3使用非阻塞式的connect的套按字已經建立連接,或者connect已經失敗.

4,其上有一個套接字錯誤等處理,對這樣的套接字進行寫操作會返回-,且,把ERROR設置成錯誤條件,可以通過指定SO_ERROR套按選項調用getsockopt獲取并清除.

上面都是理論的知識,我們現在來看一個例子,我們用select重寫http://blog.csdn.net/michael_kong_nju/article/details/43457393?中的echo_tcp_client.c中的str_cli函數:

[cpp]?view plaincopy
print?
  1. void??
  2. str_cli(FILE?*fp,?int?sockfd)??
  3. {??
  4. ????int?????????maxfdp1;??
  5. ????fd_set??????rset;??
  6. ????char????????sendline[MAXLINE],?recvline[MAXLINE];??
  7. ??
  8. ??
  9. ????FD_ZERO(&rset);??
  10. ????for?(?;?;?)?{??
  11. ????????FD_SET(fileno(fp),?&rset);??
  12. ????????FD_SET(sockfd,?&rset);??
  13. ????????maxfdp1?=?max(fileno(fp),?sockfd)?+?1;??
  14. ????????select(maxfdp1,?&rset,?NULL,?NULL,?NULL);??
  15. ??
  16. ??
  17. ????????if?(FD_ISSET(sockfd,?&rset))?{??/*?socket?is?readable?*/??
  18. ????????????if?(read(sockfd,?recvline,?MAXLINE)?==?0)??
  19. ????????????{?????
  20. ????????????????????perror("str_cli:?server?terminated?prematurely");??
  21. ????????????????????exit(1);??
  22. ????????????}??
  23. ????????????????????fputs(recvline,?stdout);??
  24. ????????}??
  25. ??
  26. ??
  27. ????????if?(FD_ISSET(fileno(fp),?&rset))?{??/*?input?is?readable?*/??
  28. ????????????if?(fgets(sendline,?MAXLINE,?fp)?==?NULL)??
  29. ????????????????return;?????/*?all?done?*/??
  30. ????????????write(sockfd,?sendline,?strlen(sendline));??
  31. ????????}??
  32. ????}??
  33. }??

限于篇幅的原因這里不給出客戶端的main函數,但是我們建議你去我的github中下載這個我已經展開過的源碼去調試運行一下:

https://github.com/michaelnju/UNPV-Relaxing-Code/blob/master/Chaper6_Select_Test/select_echo_tcp_cli.c

服務器程序還是用上一個連接中的。

這時候你會看到在客戶端和服務器正常連接的過程中,如果這時候服務器斷開了,那么客戶端會立馬被告知,而不像我們剛開始的時候那樣會阻塞。


所以我們看到了select的作用。下篇文章我們將看到select在并發服務器中的作用。


完整的客戶端代碼:

//#include "unp.h"
? /*
? Lingtao relax this code in 2015
? */
? #include <stdio.h>
? #include <sys/types.h>
? #include <sys/socket.h>
? #include <netinet/in.h>
? #include <sys/select.h>
? #include <sys/time.h>
? ?
? #define LISTENQ 5
? #define MAXLINE 2048
? #define SERV_PORT 9877
? #define max(a,b) ((a) > (b) ? (a) : (b))
? ?
? typedef struct sockaddr SA;
? ?
? void
? str_cli(FILE *fp,int sockfd)
? {
? int maxfdp1;
? fd_set rset;
? char sendline[MAXLINE], recvline[MAXLINE];
? ?
? FD_ZERO(&rset);
? for ( ; ; ) {
? FD_SET(fileno(fp), &rset);
? FD_SET(sockfd, &rset);
? maxfdp1 = max(fileno(fp), sockfd) +1;
? select(maxfdp1, &rset, NULL, NULL, NULL);
? ?
? if (FD_ISSET(sockfd, &rset)) {/* socket is readable*/
? if (read(sockfd, recvline, MAXLINE) ==0)
? {
? perror("str_cli: server terminated prematurely");
? exit(1);
? }
? fputs(recvline, stdout);
? }
? ?
? if (FD_ISSET(fileno(fp), &rset)) {/* input is readable*/
? if (fgets(sendline, MAXLINE, fp) ==NULL)
? return; /* all done */
? write(sockfd, sendline, strlen(sendline));
? }
? }
? }
? ?
? int
? main(int argc,char **argv)
? {
? int sockfd;
? struct sockaddr_in servaddr;
? ?
? if (argc != 2)
? {
? perror("usage: tcpcli <IPaddress>");
? exit(1);
? }
? sockfd = socket(AF_INET, SOCK_STREAM,0);
? ?
? bzero(&servaddr, sizeof(servaddr));
? servaddr.sin_family = AF_INET;
? servaddr.sin_port = htons(SERV_PORT);
? inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
? ?
? connect(sockfd, (SA *) &servaddr,sizeof(servaddr));
? str_cli(stdin, sockfd); /* do it all */
? ?
? exit(0);
? }


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

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

相關文章

open、read、write、文件類型

open&#xff0c;打開一個文件、創建一個文件或判斷一個文件是否存在。 頭文件&#xff1a;<sys/types.h> <sys/stat.h> <fcntl.h> 重載函數有&#xff1a;int open(const char *pathname, int flags) int open(const char *pathname, int flags, mode_t m…

用模板寫單鏈表 尹成

轉載&#xff1a;http://blog.csdn.net/itcastcpp/article/details/39081953 為了加深對模板的理解&#xff0c;我們今天一起用模板寫一個單鏈表&#xff0c;希望通過這個例子&#xff0c;能夠幫助大家加深對模板的體會&#xff0c;具體如下&#xff1a; SList.hpp內容&#xf…

lseek、stat、access、chmod、strtol、truncate、unlink

lseek&#xff0c;可實現計算文件長度&#xff0c;以及文件擴展。 int ret lseek(fd, 0, SEEK_END); //文件長度printf("file lendth %d\n", ret); int ret lseek(fd, 2000, SEEK_END); //文件拓展2000個byte 在文件末尾偏移2000printf("return va…

inode淺談

索引節點inode&#xff1a;保存的其實是實際的數據的一些信息&#xff0c;這些信息稱為“元數據”(也就是對文件屬性的描述)。例如&#xff1a;文件大小&#xff0c;設備標識符&#xff0c;用戶標識符&#xff0c;用戶組標識符&#xff0c;文件模式&#xff0c;擴展屬性&#x…

Openssl-MD5

http://blog.csdn.net/sunspider107/article/details/7395904 MD5是最常用的一個信息摘要算法&#xff0c;雖然現在慢慢被SHA1算法替代&#xff0c;但還是應用廣泛。 MD5的計算結果是16個字節。 int MD5_Init(MD5_CTX *c); 初始化MD5 Context參數&#xff1b; c: MD5 context; …

opendir、readdir以及使用

opendir&#xff0c;打開一個目錄。 函數原型&#xff1a;DIR *opendir(const char *name) DIR *fopendir(int fd) DIR是一個結構指針&#xff0c;是一個內部結構&#xff0c;保存所打開的目錄信息。函數出錯返回NULL readdir&#xff0c;讀目錄 ,<dirent.h> 函數原型&am…

Linux下C語言使用openssl庫進行MD5校驗

http://blog.csdn.net/cassie_huang/article/details/53212933 作者&#xff1a;無腦仔的小明 出處&#xff1a;http://www.cnblogs.com/wunaozai/ 我們以一個字符串為例&#xff0c;新建一個文件filename.txt&#xff0c;在文件內寫入hello &#xff0c;然后在Linux下可以使…

dup、dup2、fcntl

dup、dup2&#xff0c;復制文件描述符 int dup(int oldfd);  //返回文件描述表中沒有被占用的最小可用的描述符&#xff0c;新舊描述符作用相同 int dup2(int oldfd, int newfd);  //如果new已經被打開&#xff0c;先關閉再拷貝就會指向同一個文件&#xff0c;如果old和new…

進程

創建子進程&#xff1a;fork調用&#xff0c; 一次fork調用返回兩個值&#xff0c;1、返回子進程的pid&#xff08;非負整數&#xff09; 2、返回0 父進程的fork返回子進程的id&#xff0c;子進程的fork返回0&#xff08;表示執行成功&#xff09; 創建單個子進程&#xff1a; …

Ubuntu在vmware虛擬機無法上網的解決方法

http://blog.csdn.net/xueyushenzhou/article/details/50460183 在vmware中安裝Ubuntu之后&#xff0c;我們希望基本的功能如上網、傳輸文件等功能都是可用的&#xff0c;但是經常遇到不能上網的情況。使用筆記本時&#xff0c;我們經常希望能通過無線網卡上網&#xff0c;但是…

exec函數族

fork創建子進程后執行的是和父進程相同的程序&#xff08;但有可能執行不同的代碼分支&#xff09;&#xff0c;子進程往往要調用一種exec函數以執行另一個程序。當進程調用一種exec函數時&#xff0c;該進程的用戶空間代碼和數據完全被新程序替換&#xff0c;從新程序的啟動例…

IO 多路復用之poll總結

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

wait、waitpid

父進程調用wait函數可以回收子進程的終止信息&#xff0c;該函數有三個功能&#xff1a;&#xff08;一次wait調用回收一個子進程 回收多個用循環&#xff09; 1、阻塞等待子進程退出 2、回收子進程殘留資源 3、獲取子進程結束狀態&#xff08;退出原因&#xff09; pid_t wait…

C++項目中的extern C {}

http://www.cnblogs.com/skynet/archive/2010/07/10/1774964.html 引言 在用C的項目源碼中&#xff0c;經常會不可避免的會看到下面的代碼&#xff1a; 123456789#ifdef __cplusplusextern "C" {#endif/*...*/#ifdef __cplusplus}#endif它到底有什么用呢&#xff0c;…

管道

管道&#xff0c;其本質是一個偽文件&#xff08;實為內核緩沖區&#xff09;&#xff1b;由兩個文件描述符引用&#xff0c;一個表示讀端、一個表示寫端&#xff1b;規定數據從管道的寫端流入&#xff0c;讀端流出。 管道的原理&#xff1a;管道實為內核使用環形隊列機制&…

C/S、B/S的區別

C/S結構&#xff0c;即Client/Server(客戶機/服務器)結構&#xff0c;是大家熟知的軟件系統體系結構&#xff0c;通過將任務合理分配到Client端和Server端&#xff0c;降低了系統的通訊開銷&#xff0c;可以充分利用兩端硬件環境的優勢。早期的軟件系統多以此作為首選設計標準。…

extern c用法解析

http://www.jianshu.com/p/5d2eeeb93590 引言C保留了一部分過程式語言的特點&#xff0c;因而它可以定義不屬于任何類的全局變量和函數。但是&#xff0c;C畢竟是一種面向對象的程序設計語言&#xff0c;為了支持函數的重載&#xff0c;C對全局函數的處理方式與C有明顯的不同。…

C語言實現單鏈表(帶頭結點)的基本操作(創建,頭插法,尾插法,刪除結點,打印鏈表)

http://blog.csdn.net/xiaofeige567/article/details/27484137 C語言實現單鏈表&#xff08;帶頭結點&#xff09;的基本操作&#xff08;創建&#xff0c;頭插法&#xff0c;尾插法&#xff0c;刪除結點&#xff0c;打印鏈表&#xff09; [plain] view plaincopy #include<…

靜態網頁與動態網頁區別

一、靜態web頁面&#xff1a;1、在靜態Web程序中&#xff0c;客戶端使用Web瀏覽器&#xff08;IE、FireFox等&#xff09;經過網絡(Network)連接到服務器上&#xff0c;使用HTTP協議發起一個請求&#xff08;Request&#xff09;&#xff0c;告訴服務器我現在需要得到哪個頁面&…

單向循環鏈表C語言實現

http://blog.csdn.net/morixinguan/article/details/51771633 我們都知道&#xff0c;單向鏈表最后指向為NULL&#xff0c;也就是為空&#xff0c;那單向循環鏈表就是不指向為NULL了&#xff0c;指向頭節點&#xff0c;所以下面這個程序運行結果就是&#xff0c;你將會看到遍歷…