Linux下的TCP/IP编程----IO复用及IO复用服务端
http://blog.csdn.net/wqc_csdn/article/details/51583901
在之前我們實現的并發服務端時通過床將多個進程來實現的,這種并實現并發的方式簡單方便,但是進程的創建和銷毀是很消耗系統資源的,在訪問量大時服務器很容易出現資源不夠用的情況。除此之外,由于每個進程有獨立的內存空間,所以進程間的通訊也相對比較復雜。因此我們可以考慮通過另一種方式來實現服務端的并發服務——IO復用。
復用:
復用在通訊領域很常見,一般常見”頻分復用”,”時分復用”等名詞。其實復用就是在一個通信頻道內傳遞多個數據(信號)的技術。以頻分復用為例:其實就是在一個通信信道內,發送端通過把信息加載在不同頻率的波段上進行發送,而接受端在接受到波時通過濾波裝置把各中頻率的波進行分離,以此達到提高通信信道利用率的目的。
IO復用:
IO復用其實也是通過對IO描述符的復用來減少進程的創建,使得服務端始終只有一個進程,從而節省了系統資源,提高效率。
select()函數是最具有代表性的實現復用服務端的方法,它可以將多個文件描述符集中到一起進行統一監視,當監視到有文件描述符需要輸入或者是輸出時就選擇該接口進行通訊,通訊完成之后就回到之前監視的狀態。
監視內容:是否存在套接字接受數據?無需阻塞傳輸數據的套接字有哪些?哪些套接字發生了異常?
int select(int maxfd,fd_set *read_set, *write_set,fd_set *except_set, const struct timeval *timeout)選擇描述符進行通訊:
-
maxfd(監視數量):監視對象文件描述符數量
-
read_set(讀取文件描述符集合的地址):將所有關注”是否存在待讀取數據”的文件描述符注冊到fd_set集合中,并傳遞地址值。也就是說select()函數會監視這個集合里邊的文件描述符是是否有待讀取的數據,沒有要監聽的描述符時傳0
-
write_set(寫入文件描述符集合的地址):將所有關注”是否可傳輸無阻塞數據”的文件描述符注冊到fd_set集合中,并傳遞地址值。也就是說select()函數會監視這個集合里邊的文件描述符是否能發送無阻塞數據,沒有要監聽的描述符時傳0
-
except_set(發生異常文件描述符集合的地址):將所有關注”是否可發生異常”的文件描述符注冊到fd_set集合中,并傳遞地址值。也就是說select()函數會監視這個集合里邊的文件描述符是否發生異常,沒有要監聽的描述符時傳0
-
timeout(超時):位防止無限進入阻塞狀態,設置一個超時信息
發生錯誤時返回-1,超時時返回0,當所關注的事件發生時,返回所發生事件的文件描述符數量
select()函數的使用比較復雜,大體分為三步:
參數設置:
-
設置文件描述符:使用select()函數能同時監聽多個文件描述符,首先要使用fd_set類型將這些文件描述符按照分類(接收,傳輸,異常)集中起來。
fd_set是一個存有0和1的位數組。從下標0開始,一直到下標為當前文件描述符的最大序號為止,依次表示該文件描述符是否被監聽,例如fd_set 變量fds[0]中的值為1時表示文件描述符0(標準的輸入流)被監聽。?
針對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的信息
指定監聽范圍:指定監聽文件描述符的范圍,其實也就是fd_set中的文件描述符數量,由于每次新創建一個文件描述符時都會自動加1,所以要傳入的值為最大的文件描述符+1(加一是由于文件描述符的標號從0開始)。
設置超時:由于當文件描述符沒有狀態的改變時select()函數會始終處于阻塞狀態,設置超時時間就是為了防止無限制的等待。即使文件描述符沒有發生變化,只要過了指定時間,函數會返回0。這樣在函數調用時能知道當前的狀態。
結構體timeval用于保存設置的超時時間,每次在調用select()函數之前都要重新設置超時時間,其結構體如下:
struct timeval{long tv_sec;//秒數long tv_usec://毫秒數 }- 1
- 2
- 3
- 4
調用select()函數:監聽注冊的文件描述符的狀態,當有狀態發生變化,或者時超時時返回結果。
查看調用結果:當select()函數返回值是大于0的整數時說明是所監聽的文件描述符的狀態發生了變化,這時我們可以通過之前的fd_set變量來查看變化的結果。
當select()函數調用完之后向其傳入的fd_set變量將發生變化,原來為1的所有位均變為0,但是發生變化的文件描述符對應位除外,因此可以認為值仍為1的位置上的文件描述符發生了變化。
至此關于select()函數的介紹就結束了,用起來比較復雜,我們梳理一遍使用過程:
準備工作:
-
為select()設置要監視的文件描述符集合,使用函數庫提供的關于fd_set的宏定義設置fd_set
-
為select()設置監視范圍,即當前最大文件描述符+1
-
位select(0設置超時時間,把秒數填入timeval結構體的tv_sec成員中,把毫秒數填入timeval結構體的tv_usec成員中,每次在調用select()函數之前都要重新設置超時時間。
調用select()函數
查看調用結果:根據fd_set調用前后的變化來確定發生變化的文件描述符,調用之后fd_set中值為1的位所對應的文件描述符狀態發生了變化
調用發生變化的文件描述符進行相應的操作
在大體了解了select()函數的使用過程之后我們就可以嘗試著進行一下簡單的應用:
#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;//保存函數的返回結果int select_res;//字符串長度int str_len;//字符緩沖char buff[BUFF_SIZE];//超時時間結構體struct timeval time_out;//初始化fd_set,所有位都置0FD_ZERO(&read_set);//設置fd_set,使其監視文件描述符為0的文件描述符(系統的標準輸入流)FD_SET(0,&read_set);while(1){temp_set = read_set;//設置超時時間time_out.tv_sec = 5;time_out.tv_usec = 0;//調用select()函數select_res = select(1,&temp_set,0,0,&time_out);//根據返回值來判斷是否變化if(select_res == -1){puts("select() error");break;}else if(select_res == 0){puts("select() timeout");}else{//檢查是否含有要查詢的描述符if(FD_ISSET(0,&temp_set)){//從文件描述符為0的流中讀取數據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復用的服務端:
/*************************************************************************> File Name: echo_select_server.c> Author: xjhznick> Mail: xjhznick@gmail.com > Created Time: 2015年03月26日 星期四 14時03分40秒> Description:使用select函數實現I/O復用服務器端************************************************************************/#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){ //命令行中啟動服務程序僅限一個參數:端口號printf("Usage : %s <port>\n", argv[0]);exit(1);}//調用socket函數創建套接字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]));//調用bind函數分配IP地址和端口號if( -1 == bind( server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) ){error_handling("bind() error");}//監聽端口的連接請求,連接請求等待隊列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){//調用select之后,除發生變化的文件描述符對應的bit,其他所有位置0,所以需用保存初值,通過復制使用reads = reads_init;//調用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函數自動創建數據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//斷開連接,關閉套接字close(server_sock);return 0; }void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(EXIT_FAILURE); }總結
以上是生活随笔為你收集整理的Linux下的TCP/IP编程----IO复用及IO复用服务端的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 生过孩子后输卵管还会堵塞吗
- 下一篇: 防火墙5788剧情介绍