轉載:http://www.cnblogs.com/lizhenghn/p/3619091.html
在前面我們介紹了循環服務器,并發服務器模型。簡單的循環服務器每次只能處理一個請求,即處理的請求是串行的,效率過低;并發服務器可以通過創建多個進程或者是線程來并發的處理多個請求。但是當客戶端增加時,就需要創建更多的進程或者線程,就會導致系統負載最終轉移到進程或線程的切換開銷上。
????? 為了減少這類開銷,而使系統處理能力集中在核心業務上,就要求我們降低并發的進程或線程數目,因此又實現了一個更高級的IO復用循環服務器。I/O復用的循環服務器一般創建兩個線程,一個是客戶端連接處理線程,專門用來處理客戶端的連接,當有客戶端到來的時候,此線程把客戶端的套接字描述符放到一塊公共的區域中。另一個是業務處理線程,此線程輪循(select)客戶端套接字描述符集合中有沒有數據到來,如果有數據到來,那么就進行處理。這樣,客戶 端的增加并不會造成系統進程或線程數的明顯增加,而使其處理能力與CPU和內存直接相關。
TCP并發服務器模型 I/O多路復用模型偽代碼
/* TCP并發服務器模型 I/O多路復用 */ /* 服務器主進程 */socket();bind();listen(); pthread_create( ); //創建客戶端連接線程和業務處理線程pthread_join( ); //等待線程結束close( ); //關閉服務器套接字 /* 連接處理線程 */while(1){accept( ); //接受一個客戶端連接store();//存儲客戶端套接字描述符到一個公共集合中 }/* 業務處理線程 */while(1){get( ); //取出可用的客戶端套接字描述符select( ); //設置監聽讀寫文件描述符集合 recv( );process( );send( );close( );}
一個I/O多路復用模型的例子
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <time.h> #include <string.h> #include <stdio.h> #include <pthread.h> #include <sys/select.h> #define BUFFLEN 1024 #define SERVER_PORT 12349 #define BACKLOG 5 #define CLIENTNUM 1024 /**最大支持客戶端數量*//*可連接客戶端的文件描述符數組*/ int connect_host[CLIENTNUM]; int connect_number = 0; static void *handle_request(void *argv) { time_t now; /*時間*/char buff[BUFFLEN];/*收發數據緩沖區*/int n = 0;int maxfd = -1;/*最大偵聽文件描述符*/fd_set scanfd; /*偵聽描述符集合*/struct timeval timeout; /*超時*/timeout.tv_sec = 1; /* 阻塞1秒后超時返回 */ timeout.tv_usec = 0; int i = 0;int err = -1;for(;;){ /*最大文件描述符值初始化為-1*/ maxfd = -1;FD_ZERO(&scanfd);/*清零文件描述符集合*/for(i=0;i<CLIENTNUM;i++)/*將文件描述符放入集合*/{if(connect_host[i] != -1)/*合法的文件描述符*/{FD_SET(connect_host[i], &scanfd);/*放入集合*/if(maxfd < connect_host[i])/*更新最大文件描述符值*/{maxfd = connect_host[i];}}}/*select等待*/err = select(maxfd + 1, &scanfd, NULL, NULL, &timeout) ; switch(err){case 0:/*超時*/break;case -1:/*錯誤發生*/break;default:/*有可讀套接字文件描述符*/if(connect_number<=0)break;for(i = 0;i<CLIENTNUM;i++){/*查找激活的文件描述符*/if(connect_host[i] != -1)if(FD_ISSET(connect_host[i],&scanfd)) { memset(buff, 0, BUFFLEN);/*清零*/n = recv(connect_host[i], buff, BUFFLEN,0);/*接收發送方數據*/if(n > 0 && !strncmp(buff, "TIME", 4))/*判斷是否合法接收數據*/{memset(buff, 0, BUFFLEN);/*清零*/now = time(NULL);/*當前時間*/sprintf(buff, "%24s\r\n",ctime(&now));/*將時間拷貝入緩沖區*/send(connect_host[i], buff, strlen(buff),0);/*發送數據*/}/*關閉客戶端*/close(connect_host[i]); /*更新文件描述符在數組中的值*/connect_host[i] = -1;connect_number --; /*客戶端計數器減1*/ } }break; } } return NULL; }static void *handle_connect(void *argv) { int s_s = *((int*)argv) ;/*獲得服務器偵聽套接字文件描述符*/int s_c = -1;/*連接客戶端文件描述符*/struct sockaddr_in from;int len = sizeof(from);/*接收客戶端連接*/for(;;){int i = 0;int s_c = accept(s_s, (struct sockaddr*)&from, &len);/*接收客戶端的請求*/printf("a client connect, from:%s\n",inet_ntoa(from.sin_addr));/*查找合適位置,將客戶端的文件描述符放入*/ for(i=0;i<CLIENTNUM;i++){if(connect_host[i] == -1)/*找到*/{/*放入*/connect_host[i]= s_c;/*客戶端計數器加1*/connect_number ++;/*繼續輪詢等待客戶端連接*/break; } } } return NULL; }int main(int argc, char *argv[]) {int s_s; /*服務器套接字文件描述符*/struct sockaddr_in local; /*本地地址*/ int i = 0;memset(connect_host, -1, CLIENTNUM);/*建立TCP套接字*/s_s = socket(AF_INET, SOCK_STREAM, 0);/*初始化地址接哦股*/memset(&local, 0, sizeof(local));/*清零*/local.sin_family = AF_INET;/*AF_INET協議族*/local.sin_addr.s_addr = htonl(INADDR_ANY);/*任意本地地址*/local.sin_port = htons(SERVER_PORT);/*服務器端口*//*將套接字文件描述符綁定到本地地址和端口*/int err = bind(s_s, (struct sockaddr*)&local, sizeof(local));err = listen(s_s, BACKLOG);/*偵聽*/pthread_t thread_do[2];/*線程ID*//*創建線程處理客戶端連接*/pthread_create(&thread_do[0],/*線程ID*/NULL,/*屬性*/handle_connect,/*線程回調函數*/(void*)&s_s); /*線程參數*//*創建線程處理客戶端請求*/ pthread_create(&thread_do[1],/*線程ID*/NULL,/*屬性*/handle_request,/*線程回調函數*/NULL); /*線程參數*//*等待線程結束*/for(i=0;i<2;i++)pthread_join(thread_do[i], NULL);close(s_s);return 0; }