socket編程:多工I/O服務端用戶端。

來源:互聯網
上載者:User

標籤:伺服器   多線程   服務端   用戶端   局限性   多工   

其實在之前的TCP之中,我們編程實現了多進程,多線程機制下的TCP伺服器,但是對於這種的TCP伺服器而言,存在太大的資源局限性。所以我們可以是用I/0模型中的多工I/O模型來進行編程。


他的具體思想就是:當前進程可以處理多個相應時間,記錄多個描述符,然後控制輪詢時間態,當有響應產生的時候我們就去儲存當前回應檔描述符,對他進行串連處理/資料轉送就OK了。在一個進程進行多個回應時間的回覆情況下,可以大大的節約我們系統所消耗的資源。


對這個進行操作的函數就是select():

他的函數原型如下:

650) this.width=650;" src="http://s1.51cto.com/wyfs02/M02/80/F1/wKioL1dFROezZP3zAAB9ATIP8PU712.png" title="QQ圖片20160525141925.png" alt="wKioL1dFROezZP3zAAB9ATIP8PU712.png" />

首先我們不看select函數本身,我們先去看一下select下面的幾個FD函數:

FD = fd,其實很簡單,就是我們的檔案描述符操作函數。

關於fd_set*類型的變數:其實就是一個對某種類型時間行為的檔案描述符集合,稱為描述片語。

FD_CLR(inr fd,fd_set* set);用來清除描述片語set中相關fd 的位

FD_ISSET(int fd,fd_set *set);用來測試描述片語set中相關fd 的位是否為真

FD_SET(int fd,fd_set*set);用來設定描述片語set中相關fd的位

FD_ZERO(fd_set *set);用來清除描述片語set的全部位。


然後我們來看一下select函數:

參數nfds是需要監視的最大的檔案描述符值+1;

rdset,wrset,exset分別對應於需要檢測的可讀檔案描述符的集合,可寫檔案描述符的集合及異

常檔案描述符的集合。

struct timeval結構用於描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件

發生則函數返回,傳回值為0。

參數timeout為結構timeval,用來設定select()的等待時間,其結構定義如下:

650) this.width=650;" src="http://s3.51cto.com/wyfs02/M02/80/F2/wKiom1dFRVPBQZNdAAAmTwJH5NA018.png" title="QQ圖片20160525142509.png" alt="wKiom1dFRVPBQZNdAAAmTwJH5NA018.png" />

如果參數timeout設為:

NULL:則表示select()沒有timeout,select將一直被阻塞,直到某個檔案描述符上發生了

事件。

0:僅檢測描述符集合的狀態,然後立即返回,並不等待外來事件的發生。

特定的時間值:如果在指定的時間段裡沒有事件發生,select將逾時返回。


函數傳回值:

執行成功則返迴文件描述詞狀態已改變的個數

如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回;

當有錯誤發生時則返回-1,錯誤原因存於errno,此時參數readfds,writefds,exceptfds和

timeout的值變成不可預測。錯誤值可能為:

EBADF 檔案描述詞為無效的或該檔案已關閉。

EINTR 此調用被訊號所中斷。

EINVAL 參數n 為負值。

ENOMEM 核心記憶體不足。


理解select模型的關鍵在於理解fd_set,為說明方便,取fd_set長度為1位元組,fd_set中的每一bit

可以對應一個檔案描述符fd。則1位元組長的fd_set最大可以對應8個fd。

(1)執行fd_set set; FD_ZERO(&set);則set用位表示是0000,0000。

(2)若fd=5,執行FD_SET(fd,&set);後set變為0001,0000(第5位置為1)

(3)若再加入fd=2,fd=1,則set變為0001,0011

(4)執行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都發生可讀事件,則select返回,此時set變為0000,0011。注意:沒有事件

發生的fd=5被清空。

 基於上面的討論,可以輕鬆得出select模型的特點:

  (1)可監控的檔案描述符個數取決與sizeof(fd_set)的值。我這邊服務 器上sizeof(fd_set)=

512,每bit表示一個檔案描述符,則我伺服器上支援的最大檔案描述符是512*8=4096。據說

可調,另有說雖然可調,但調整上限受於編譯核心時的變數值。本人對調整fd_set的大小不

太感興趣,參考http://www.cppblog.com /CppExplore/archive/2008/03/21/45061.html中的模

型2(1)可以有效突破select可監控的檔案描述符上 限。

  (2)將fd加入select監控集的同時,還要再使用一個資料結構array儲存放到select監控集

中的fd,一是用於再select 返回後,array作為來源資料和fd_set進行FD_ISSET判斷。二是select

返回後會把以前加入的但並無事件發生的fd清空,則每次開始 select前都要重新從array取得fd

逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用於select的第一個參數。

  (3)可見select模型必須在select前迴圈array(加fd,取maxfd),select返回後迴圈array

(FD_ISSET判斷是否有時間發生)。


對於利用select函數所編寫的伺服器端而言:

  1. TCP服務端所產生的所有sock通訊端描述符(檔案描述符),其實都可以歸納到可讀檔案描述符集合中。

  2. 當我們檢測到listen()的sock通訊端就建立串連accept(),如果確認到是普通,也就是我們accept()所產生的檔案描述符時,就確定這個是一條對於用戶端/服務端的串連鏈路。然後我們就利用相關的函數操作進行資訊傳輸,

  3. timeout代表當次所進行的詢問等待時間,需要注意的是就是每一次迴圈都必須重新設定timeout。

  4. 每一次迴圈開始都要對描述片語進行初始化,要不然當timeout耗盡而沒有產生響應的話,會改變描述片語的值,從而產生錯誤。


select缺點:

(1)每次調用select,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很大

(2)同時每次調用select都需要在核心遍曆傳遞進來的所有fd,這個開銷在fd很多時也很大

(3)select支援的檔案描述符數量太小了,預設是1024。


下面是I/O多工代碼:

伺服器端:

#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<stdlib.h>#include<netinet/in.h>#include<arpa/inet.h>#include<unistd.h>#include<error.h>#include<assert.h>#define _BACKLOG_ 5int fds[64]; //檔案描述符集合//命令列參數出錯static void usage(const char *proc){printf("usage : %s [ip][port]\n",proc);}//伺服器端TCP的連結綁定監聽狀態。static int startup(char *ip,int port){assert(ip);int sock = socket(AF_INET,SOCK_STREAM,0);if(sock < 0){perror("socket");exit(1);}int set = 1;        if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set)) < 0)        {               perror("setsockopt");            exit(4);        }struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = inet_addr(ip);if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){perror("bind");exit(2);}if(listen(sock,_BACKLOG_) < 0){perror("listen");exit(3);}return sock;}int main(int argc,char *argv[]){//判斷命令參數行.if(argc != 3){usage(argv[0]);exit(1);}int port = atoi(argv[2]);char *ip = argv[1];int listen_sock = startup(ip,port);int done = 0;int new_sock = -1;struct sockaddr_in client;socklen_t len = sizeof(client);int max_fd; //selet函數的第一個記錄參數///讀寫事件檔案描述符fd_set reads;fd_set writes;int i = 0;int fds_num = sizeof(fds)/sizeof(fds[0]);//對檔案描述符集合進行初始化for(;i < fds_num;++i){fds[i] = -1;}//當前存在listen檔案描述符。fds[0] = listen_sock;max_fd = fds[0];//初次調用select函數設定max_fd的值;while(!done){//初始化讀寫檔案描述符FD_ZERO(&reads);FD_ZERO(&writes);//將listen_sock設定為reads,因為時在等待請求相應,相當與當前的讀取操作。FD_SET(listen_sock,&reads);//設定多工中的輪尋時間表。struct timeval timeout = {5,0};//沒次讀取添加事件到reads中。;for(i = 1;i < fds_num; ++i){if(fds[i] >0){FD_SET(fds[i],&reads);if(fds[i] > max_fd){max_fd = fds[i];}}}switch(select(max_fd+1, &reads,&writes,NULL,NULL)){case 0 ://timeout{printf("select timeout");break;}case -1:{//errorperror("select");break;}default:{//返回改變了的檔案描述.i = 0;//遍曆所有的檔案描述符集合。for(;i<fds_num;++i){//確認是否時監聽時間,是的話就緒要accept;if(fds[i] == listen_sock && FD_ISSET(fds[i],&reads)){new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);if(new_sock <0){perror("accept");continue;}printf("get a new connet...%d\n",new_sock);for(i = 0;i< fds_num;++i){if(fds[i] == -1){fds[i] = new_sock;break;}}if(i == fds_num){close(new_sock);}}else if(fds[i] > 0 &&FD_ISSET(fds[i],&reads))//正常事件,但是是非監聽時間,也就代表時建立立的new_sock。{char buf[1024];ssize_t s = read(fds[i],buf,sizeof(buf) -1);if(s > 0){buf[s] = ‘\0‘;//printf("client : %s\n",buf);printf("client : %s",buf);write(fds[i],buf,sizeof(s)+1);}else if(s == 0){printf("client quit...\n");close(fds[i]);fds[i] = -1;}else{}}else{}}}break;}}return 0;}

用戶端:

#include<stdio.h>#include<unistd.h>#include<string.h>#include<error.h>#include<sys/types.h>#include<sys/socket.h>#include<arpa/inet.h>#define _PORT_ 8111void process_conn_client(int s){ssize_t size  = 0;char buffer[1024];while(1){size = read(0,buffer,1024);if(size > 0){write(s,buffer,size);size = read(s,buffer,1024);buffer[size] = ‘\0‘;printf("server:%s",buffer);//write(1,buffer,size);}}}int main(){int s;struct sockaddr_in server_sock;s = socket(AF_INET,SOCK_STREAM,0);if(s < 0){printf("sock error");return 1;}bzero(&server_sock,sizeof(server_sock));server_sock.sin_family = AF_INET;server_sock.sin_addr.s_addr = htonl(INADDR_ANY);server_sock.sin_port = htons(_PORT_);connect(s,(struct sockaddr *)&server_sock,sizeof(server_sock));process_conn_client(s);close(s);return 0;}

運行結果;

650) this.width=650;" src="http://s4.51cto.com/wyfs02/M01/80/F3/wKiom1dFS4Gi5PATAACx9qqx5X4707.png" title="QQ圖片20160525145105.png" alt="wKiom1dFS4Gi5PATAACx9qqx5X4707.png" />

本文出自 “剩蛋君” 部落格,請務必保留此出處http://memory73.blog.51cto.com/10530560/1783014

socket編程:多工I/O服務端用戶端。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.