Linux下的TCP/IP编程----IO复用及IO复用服务端
http://blog.csdn.net/wqc_csdn/article/details/51583901
在之前我們實現(xiàn)的并發(fā)服務(wù)端時通過床將多個進程來實現(xiàn)的,這種并實現(xiàn)并發(fā)的方式簡單方便,但是進程的創(chuàng)建和銷毀是很消耗系統(tǒng)資源的,在訪問量大時服務(wù)器很容易出現(xiàn)資源不夠用的情況。除此之外,由于每個進程有獨立的內(nèi)存空間,所以進程間的通訊也相對比較復(fù)雜。因此我們可以考慮通過另一種方式來實現(xiàn)服務(wù)端的并發(fā)服務(wù)——IO復(fù)用。
復(fù)用:
復(fù)用在通訊領(lǐng)域很常見,一般常見”頻分復(fù)用”,”時分復(fù)用”等名詞。其實復(fù)用就是在一個通信頻道內(nèi)傳遞多個數(shù)據(jù)(信號)的技術(shù)。以頻分復(fù)用為例:其實就是在一個通信信道內(nèi),發(fā)送端通過把信息加載在不同頻率的波段上進行發(fā)送,而接受端在接受到波時通過濾波裝置把各中頻率的波進行分離,以此達到提高通信信道利用率的目的。
IO復(fù)用:
IO復(fù)用其實也是通過對IO描述符的復(fù)用來減少進程的創(chuàng)建,使得服務(wù)端始終只有一個進程,從而節(jié)省了系統(tǒng)資源,提高效率。
select()函數(shù)是最具有代表性的實現(xiàn)復(fù)用服務(wù)端的方法,它可以將多個文件描述符集中到一起進行統(tǒng)一監(jiān)視,當(dāng)監(jiān)視到有文件描述符需要輸入或者是輸出時就選擇該接口進行通訊,通訊完成之后就回到之前監(jiān)視的狀態(tài)。
監(jiān)視內(nèi)容:是否存在套接字接受數(shù)據(jù)?無需阻塞傳輸數(shù)據(jù)的套接字有哪些?哪些套接字發(fā)生了異常?
int select(int maxfd,fd_set *read_set, *write_set,fd_set *except_set, const struct timeval *timeout)選擇描述符進行通訊:
-
maxfd(監(jiān)視數(shù)量):監(jiān)視對象文件描述符數(shù)量
-
read_set(讀取文件描述符集合的地址):將所有關(guān)注”是否存在待讀取數(shù)據(jù)”的文件描述符注冊到fd_set集合中,并傳遞地址值。也就是說select()函數(shù)會監(jiān)視這個集合里邊的文件描述符是是否有待讀取的數(shù)據(jù),沒有要監(jiān)聽的描述符時傳0
-
write_set(寫入文件描述符集合的地址):將所有關(guān)注”是否可傳輸無阻塞數(shù)據(jù)”的文件描述符注冊到fd_set集合中,并傳遞地址值。也就是說select()函數(shù)會監(jiān)視這個集合里邊的文件描述符是否能發(fā)送無阻塞數(shù)據(jù),沒有要監(jiān)聽的描述符時傳0
-
except_set(發(fā)生異常文件描述符集合的地址):將所有關(guān)注”是否可發(fā)生異常”的文件描述符注冊到fd_set集合中,并傳遞地址值。也就是說select()函數(shù)會監(jiān)視這個集合里邊的文件描述符是否發(fā)生異常,沒有要監(jiān)聽的描述符時傳0
-
timeout(超時):位防止無限進入阻塞狀態(tài),設(shè)置一個超時信息
發(fā)生錯誤時返回-1,超時時返回0,當(dāng)所關(guān)注的事件發(fā)生時,返回所發(fā)生事件的文件描述符數(shù)量
select()函數(shù)的使用比較復(fù)雜,大體分為三步:
參數(shù)設(shè)置:
-
設(shè)置文件描述符:使用select()函數(shù)能同時監(jiān)聽多個文件描述符,首先要使用fd_set類型將這些文件描述符按照分類(接收,傳輸,異常)集中起來。
fd_set是一個存有0和1的位數(shù)組。從下標0開始,一直到下標為當(dāng)前文件描述符的最大序號為止,依次表示該文件描述符是否被監(jiān)聽,例如fd_set 變量fds[0]中的值為1時表示文件描述符0(標準的輸入流)被監(jiān)聽。?
針對fd_set的操作都是以位為單位的,為此專門編寫了用于fd_set讀寫的宏定義: -
FD_ZERO(fd_set *fdset):將fd_set的所有位初始化為0
-
FD_SET(int fd,fd_set *fdset):在fd_set中注冊文件描述符fd的信息
-
FD_CLR(int fd,fd_set *fdset):從fd_set中清除文件描述符fd的信息
-
FD_ISSET(int fd,fd_set *fdset):查詢fd_set中是否包含文件描述符fd的信息
指定監(jiān)聽范圍:指定監(jiān)聽文件描述符的范圍,其實也就是fd_set中的文件描述符數(shù)量,由于每次新創(chuàng)建一個文件描述符時都會自動加1,所以要傳入的值為最大的文件描述符+1(加一是由于文件描述符的標號從0開始)。
設(shè)置超時:由于當(dāng)文件描述符沒有狀態(tài)的改變時select()函數(shù)會始終處于阻塞狀態(tài),設(shè)置超時時間就是為了防止無限制的等待。即使文件描述符沒有發(fā)生變化,只要過了指定時間,函數(shù)會返回0。這樣在函數(shù)調(diào)用時能知道當(dāng)前的狀態(tài)。
結(jié)構(gòu)體timeval用于保存設(shè)置的超時時間,每次在調(diào)用select()函數(shù)之前都要重新設(shè)置超時時間,其結(jié)構(gòu)體如下:
struct timeval{long tv_sec;//秒數(shù)long tv_usec://毫秒數(shù) }- 1
- 2
- 3
- 4
調(diào)用select()函數(shù):監(jiān)聽注冊的文件描述符的狀態(tài),當(dāng)有狀態(tài)發(fā)生變化,或者時超時時返回結(jié)果。
查看調(diào)用結(jié)果:當(dāng)select()函數(shù)返回值是大于0的整數(shù)時說明是所監(jiān)聽的文件描述符的狀態(tài)發(fā)生了變化,這時我們可以通過之前的fd_set變量來查看變化的結(jié)果。
當(dāng)select()函數(shù)調(diào)用完之后向其傳入的fd_set變量將發(fā)生變化,原來為1的所有位均變?yōu)?,但是發(fā)生變化的文件描述符對應(yīng)位除外,因此可以認為值仍為1的位置上的文件描述符發(fā)生了變化。
至此關(guān)于select()函數(shù)的介紹就結(jié)束了,用起來比較復(fù)雜,我們梳理一遍使用過程:
準備工作:
-
為select()設(shè)置要監(jiān)視的文件描述符集合,使用函數(shù)庫提供的關(guān)于fd_set的宏定義設(shè)置fd_set
-
為select()設(shè)置監(jiān)視范圍,即當(dāng)前最大文件描述符+1
-
位select(0設(shè)置超時時間,把秒數(shù)填入timeval結(jié)構(gòu)體的tv_sec成員中,把毫秒數(shù)填入timeval結(jié)構(gòu)體的tv_usec成員中,每次在調(diào)用select()函數(shù)之前都要重新設(shè)置超時時間。
調(diào)用select()函數(shù)
查看調(diào)用結(jié)果:根據(jù)fd_set調(diào)用前后的變化來確定發(fā)生變化的文件描述符,調(diào)用之后fd_set中值為1的位所對應(yīng)的文件描述符狀態(tài)發(fā)生了變化
調(diào)用發(fā)生變化的文件描述符進行相應(yīng)的操作
在大體了解了select()函數(shù)的使用過程之后我們就可以嘗試著進行一下簡單的應(yīng)用:
#include<stdio.h> #include<unistd.h> #include<sys/time.h> #include<sys/select.h>#define BUFF_SIZE 30int main(){//聲明文件描述符集合fd_set read_set;fd_set temp_set;//保存函數(shù)的返回結(jié)果int select_res;//字符串長度int str_len;//字符緩沖char buff[BUFF_SIZE];//超時時間結(jié)構(gòu)體struct timeval time_out;//初始化fd_set,所有位都置0FD_ZERO(&read_set);//設(shè)置fd_set,使其監(jiān)視文件描述符為0的文件描述符(系統(tǒng)的標準輸入流)FD_SET(0,&read_set);while(1){temp_set = read_set;//設(shè)置超時時間time_out.tv_sec = 5;time_out.tv_usec = 0;//調(diào)用select()函數(shù)select_res = select(1,&temp_set,0,0,&time_out);//根據(jù)返回值來判斷是否變化if(select_res == -1){puts("select() error");break;}else if(select_res == 0){puts("select() timeout");}else{//檢查是否含有要查詢的描述符if(FD_ISSET(0,&temp_set)){//從文件描述符為0的流中讀取數(shù)據(jù)str_len = read(0,buff,BUFF_SIZE);buff[str_len] = 0;printf("message from console : %s ",buff);}}}return 0; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
IO復(fù)用的服務(wù)端:
/*************************************************************************> File Name: echo_select_server.c> Author: xjhznick> Mail: xjhznick@gmail.com > Created Time: 2015年03月26日 星期四 14時03分40秒> Description:使用select函數(shù)實現(xiàn)I/O復(fù)用服務(wù)器端************************************************************************/#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #include<sys/time.h> #include<sys/select.h>void error_handling(char *message);#define BUFF_SIZE 32int main(int argc, char *argv[]) {int server_sock;int client_sock;struct sockaddr_in server_addr;struct sockaddr_in client_addr;socklen_t client_addr_size;char buff[BUFF_SIZE];fd_set reads, reads_init;struct timeval timeout, timeout_init;int str_len, i, fd_max, fd_num;if(argc!=2){ //命令行中啟動服務(wù)程序僅限一個參數(shù):端口號printf("Usage : %s <port>\n", argv[0]);exit(1);}//調(diào)用socket函數(shù)創(chuàng)建套接字server_sock = socket(PF_INET, SOCK_STREAM, 0);if(-1 == server_sock){error_handling("socket() error.");}memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(atoi(argv[1]));//調(diào)用bind函數(shù)分配IP地址和端口號if( -1 == bind( server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) ){error_handling("bind() error");}//監(jiān)聽端口的連接請求,連接請求等待隊列size為5if( -1 == listen(server_sock, 5) ){error_handling("listen() error");}//register fd_set varFD_ZERO(&reads_init);FD_SET(server_sock, &reads_init);//monitor socket: server_sockFD_SET(0, &reads_init);// stdin also worksfd_max = server_sock;//timeout_init.tv_sec = 5;timeout_init.tv_usec= 0;while(1){//調(diào)用select之后,除發(fā)生變化的文件描述符對應(yīng)的bit,其他所有位置0,所以需用保存初值,通過復(fù)制使用reads = reads_init;//調(diào)用select之后,timeval成員值被置為超時前剩余的時間,因此使用時也需要每次用初值重新初始化timeout = timeout_init;fd_num = select(fd_max+1, &reads, NULL, NULL, &timeout);if(fd_num < 0){fputs("Error select()!", stderr);break;}else if(fd_num == 0){puts("Time-out!");continue;}for(i=0; i<=fd_max; i++){if(FD_ISSET(i, &reads)){if(i == server_sock){//connection request!//接受連接請求client_addr_size = sizeof(client_addr);client_sock = accept( server_sock, (struct sockaddr*)&client_addr, &client_addr_size );//accept函數(shù)自動創(chuàng)建數(shù)據(jù)I/0 socketif(-1 == client_sock){error_handling("accept() error");//健壯性不佳,程序崩潰退出} else{//注冊與客戶端連接的套接字文件描述符FD_SET(client_sock, &reads_init);if(fd_max < client_sock) fd_max = client_sock;printf("Connected client : %d\n", client_sock);}}else{//read message!str_len = read(i, buff, BUFF_SIZE);if(str_len){//echo to clientbuff[str_len] = 0;printf("Message from client %d: %s", i, buff);write(i, buff, str_len);}else{ //close connectionFD_CLR(i, &reads_init);close(i);printf("Disconnected client %d!\n", i);}}//end of i==server_sock}//end of if(FD_ISSET)}//end of for}//end of while//斷開連接,關(guān)閉套接字close(server_sock);return 0; }void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(EXIT_FAILURE); }總結(jié)
以上是生活随笔為你收集整理的Linux下的TCP/IP编程----IO复用及IO复用服务端的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 生过孩子后输卵管还会堵塞吗
- 下一篇: Linux函数--inet_pton /