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()函數的使用比較復雜,大體分為三步:

  1. 參數設置:

    • 設置文件描述符:使用select()函數能同時監聽多個文件描述符,首先要使用fd_set類型將這些文件描述符按照分類(接收,傳輸,異常)集中起來。

      fd_set是一個存有0和1的位數組。從下標0開始,一直到下標為當前文件描述符的最大序號為止,依次表示該文件描述符是否被監聽,例如fd_set 變量fds[0]中的值為1時表示文件描述符0(標準的輸入流)被監聽。?
      針對fd_set的操作都是以位為單位的,為此專門編寫了用于fd_set讀寫的宏定義:

      1. FD_ZERO(fd_set *fdset):將fd_set的所有位初始化為0

      2. FD_SET(int fd,fd_set *fdset):在fd_set中注冊文件描述符fd的信息

      3. FD_CLR(int fd,fd_set *fdset):從fd_set中清除文件描述符fd的信息

      4. 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
  2. 調用select()函數:監聽注冊的文件描述符的狀態,當有狀態發生變化,或者時超時時返回結果。

  3. 查看調用結果:當select()函數返回值是大于0的整數時說明是所監聽的文件描述符的狀態發生了變化,這時我們可以通過之前的fd_set變量來查看變化的結果。

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


至此關于select()函數的介紹就結束了,用起來比較復雜,我們梳理一遍使用過程:

  1. 準備工作:

    • 為select()設置要監視的文件描述符集合,使用函數庫提供的關于fd_set的宏定義設置fd_set

    • 為select()設置監視范圍,即當前最大文件描述符+1

    • 位select(0設置超時時間,把秒數填入timeval結構體的tv_sec成員中,把毫秒數填入timeval結構體的tv_usec成員中,每次在調用select()函數之前都要重新設置超時時間。

  2. 調用select()函數

  3. 查看調用結果:根據fd_set調用前后的變化來確定發生變化的文件描述符,調用之后fd_set中值為1的位所對應的文件描述符狀態發生了變化

  4. 調用發生變化的文件描述符進行相應的操作


在大體了解了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);
}

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

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

相關文章

UVa120

相當于是一個模擬&#xff0c;為了得到合適的順序&#xff0c;我們的策略是每次找到當前沒有被翻的里面最大的&#xff0c;然后把他翻到最前面&#xff0c;然后再翻到合適的位置。 需要判斷一下當前是否已經有序&#xff0c;有序就不用翻了。 如果在最開頭找到了合適的當前應…

二叉樹的相關操作

二叉樹的數據結構 typedef char SearchTreeType; typedef struct SearchTreeNode { SearchTreeType key; // 關鍵碼 struct SearchTreeNode* lchild; struct SearchTreeNode* rchild; } SearchTreeNode; 二叉樹的初始化 由于我們是用一個指向根節點的指針表示一個二叉樹, …

網絡層:網關協議

一. 網關 所謂的網管即就是之前路由器的名字, 即路由器和網關是一個東西 二. 內部網關協議 1. RIP協議 路由信息協議 RIP 是內部網關協議 IGP中最先得到的廣泛使用的協議. 同時 RIP 是一種分布式基于距離向量的路由選擇協議. RIP 協議要求網絡中的每一個路由都必須維護自己…

UVa1152

題意很好理解&#xff0c;就是從四個集合里面取出四個數字的和為0&#xff0c;問有多少種取法。 直接枚舉肯定是會超時的&#xff0c;所以得想辦法優化一下。我們可以將兩個集合的所有的和都放在一個數組里面&#xff0c;這樣得到兩個數組&#xff0c;然后排序&#xff0c;對第…

Linux函數--inet_pton / inet_ntop

http://blog.csdn.net/lindyl/article/details/10427925 inet_pton 和 inet_ntop Linux下這2個IP地址轉換函數&#xff0c;可以在將IP地址在“點分十進制”和“整數”之間轉換而且&#xff0c;inet_pton和inet_ntop這2個函數能夠處理ipv4和ipv6。算是比較新的函數了。 inet_pto…

網絡基礎: 淺析應用層一

應用層 1. http協議 在 http 中協議分為了協議方案名, 登錄信息名, 服務器地址, 服務器端口號(http協議綁定的端口號), 文件類型, 查詢的字符串, 片段標識位 2. http 請求協議格式 httpp 總共分為三大部分, 其中首行即就是第一部分, 分為三個區域, 第一去個區域是請方法, 第…

socket 編程篇六之IPO多路復用-select poll epoll

http://blog.csdn.net/woxiaohahaa/article/details/51498951 文章參考自&#xff1a;http://blog.csdn.net/tennysonsky/article/details/45745887&#xff08;秋葉原 — Mike VS 麥克《Linux系統編程——I/O多路復用select、poll、epoll的區別使用》&#xff09; 此外&#x…

UVa11054

挺簡單的小模擬。 還是要注意思維的方向&#xff0c;要有切入點&#xff0c;不能像無頭蒼蠅一樣東想一下西想一下&#xff0c;而應該分析問題的性質&#xff0c;然后嘗試思維的方向&#xff0c;從不同的方向思考&#xff08;順序&#xff0c;逆序&#xff0c;排序后&#xff0…

淺談傳輸層

1. 傳輸層的作用 在傳輸層中有兩個特別重要的協議 TCP/UDP . 以快遞員送快遞為例說明這個問題吧. 在進行包裹傳輸的過程中快遞員需要根據快遞上的目的地址(目的計算機)來投遞包裹(IP數據報), 加入在快遞單上只寫了收件人的所在地, 所在單位, 而只寫了收件人的姓沒有寫收件人的…

UVa10375

題目描述很簡單,就是求兩個組合數的商.可是數字范圍很大,肯定不能直接計算. 因此要用到唯一分解定理,即將結果全部表示為素因子的冪的形式. #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #include<cctype> #inc…

I/O復用的 select poll和epoll的簡單實現

http://www.cnblogs.com/wj9012/p/3876734.html 一個tcp的客戶端服務器程序 服務器端不變&#xff0c;客戶端通過I/O復用輪詢鍵盤輸入與socket輸入&#xff08;接收客戶端的信息&#xff09; 服務器端&#xff1a; 1 /*服務器:2 1.客戶端關閉后&#xff0c;服務器再向客戶端發送…

netstat 相關命令解析

1.列出所有的端口 netstat -a 列出TCP協議的端口 netstat -at UDP協議的端口 netstat -au 2.列出處于監聽狀態的socket netstat -l 列出監聽的TCP端口 netstat -lt 列出監聽的UDP端口 netstat -lu 列出監聽的UNIX端口 netstat -lx 3.列出協議的統計信息 nestat …

UVa10791

我們可以先用唯一分解定理將這個數字分解成素因子冪的乘積&#xff0c;為了得到最小的和&#xff0c;我們可以發現&#xff1a;每個 素因子的冪單獨分開的和是最小的。 先說明每個素因子都是以出現的最大的次數出現。因為最小公倍數一定&#xff0c;因此至少有一個數字的這個素…

TCP相關代碼

TCP 基礎代碼 //tcp_server.c #include<stdio.h> #include<error.h> #include<sys/types.h> #include<string.h> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include <arpa/inet.h> #include<st…

UVa1635

我們很容易發現最后每一項的系數就是二項式展開&#xff0c;余數和m沒有關系就意味著可以被m整除&#xff0c;因此我們就需要求出每一個二項式的系數。但是數據實在太大我們根據唯一分解定理將m和系數都進行分解&#xff0c;然后比較因子的冪。 二項式的計算我們可以根據楊輝三…

幾種并發服務器模型的實現:多線程,多進程,select,poll,epoll

http://www.cnblogs.com/wj9012/p/3879605.html 客戶端使用select模型&#xff1a; 1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 #include <errno.h>5 #include <sys/types.h>6 #include <sys/socket.h>7 #include …

哈希表1

1. 初始化 void HashInit(HashTable* ht, HashFunc func) {if(ht NULL || func NULL){return;}ht -> size 0;ht -> func func;int i 0;for(; i < HashMaxSize; i){ht -> data[i].state Empty;} } 2. 哈希表的銷毀 void HashDestroy(HashTable* ht) {if(ht…

UVa10820

實質上就是求歐拉函數值 書上有個板子挺好&#xff0c;也不難理解。 #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #include<cctype> #include<queue> #include<set>using namespace std;typedef l…

linux socket 編程(C語言)

https://www.cnblogs.com/x_wukong/p/4541010.html 最近看了一些網絡編程的書籍&#xff0c;一直以來總感覺網絡編程神秘莫測&#xff0c;其實網絡編程入門還是很容易學的&#xff0c;下面這些代碼是我在linux下編寫的&#xff0c;已經運行過了&#xff0c;編譯之后就可以運行了…

哈希表2

哈希表的初始化 void HashInit(HashTable* ht, HashFunc func) {if(ht NULL){return;}ht -> size 0;ht -> func func;size_t i 0;for(; i < MaxSize; i){ht -> data[i] NULL;} } 哈希表的結點創建 HashElem* CreateHashElemNode(KeyType key, ValueType va…