網路程式設計(4)select函式實現I/O多路複用伺服器
我按理解整了個基於select模式的單程序多路複用併發伺服器,並寫了個簡單的測試程式測了下,雖然離實用還差得遠,但用來練習select夠用了。
至於如何實現的細節,程式碼註釋算比較清楚,就不多弄了。
一。伺服器部份
單程序併發伺服器程式碼:
/************************************************* Author: xiongchuanliang Description: I/O複用(非同步阻塞)模式_單程序+select模式伺服器 編譯命令: Linux: g++ -g -o tcpserverasynselect2 tcpserverasynselect2.cpp -m64 -I./common ./tcpserverasynselect2 **************************************************/ // 客戶端程式碼 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "initsock.h" #include "common.h" #include <time.h> //客戶端Socket資訊結構體 typedef struct _client_sock{ int fd; //客戶端socket描述符 struct sockaddr_in addr; //客戶端地址資訊結構體 time_t lastseconds; //可依這個計算空閒時間,空閒太長的連線可以關閉。 } client_sock; CInitSock initSock; //#define IDLE_MAXTIME xxx //最長空閒時長 DEMO忽略 //#define SELECT_MAXWAITTIME xxxxx #define NET_TIMEOUT 5000 //傳送超時時限 5s int main(int argc, char* argv[]) { //fd_set 是通過bit位來存放檔案描述符,可通過sizeof(fd_set) * 8 //來得可支援的最大檔案描述符數,但受系統限制,基本達不到 fd_set readset; //select()函式 readset int nSelectMaxfd = 0; //select() maxfdp引數 int nSelectRet = 0; //select() 返回值 //int nCheckTimeval = 5; //輪詢間隔 SOCKET sListen,sClient,recvSockfd; client_sock arrClientSock[FD_SETSIZE]; //存放需要select()監控的fd. int arrClientSockConnAmt = 0; //實際監控fd數 socklen_t nAddrlen = sizeof(struct sockaddr_in); time_t tCurrSysTime; char recvData[MAXDATASIZE]={0}; int i = 0 ; //建立套接字 sListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(sListen == INVALID_SOCKET) { PrintError("socket() failed.\n"); exit(EXIT_FAILURE); } //bind() 地址可立即重用 int nResAddr = 1; setsockopt( sListen, SOL_SOCKET, SO_REUSEADDR, (const char*)&nResAddr, sizeof(nResAddr) ); int nNetTimeout = NET_TIMEOUT; //設定傳送超時時限 setsockopt(sListen,SOL_SOCKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int) ); //設定接收超時時限 setsockopt(sListen,SOL_SOCKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int)); //繫結本地IP和埠到套接字 struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVPORT); //大於1024且小於65535 server_addr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_addr.sin_zero),8); if(bind(sListen,(struct sockaddr *)&server_addr,sizeof(struct sockaddr)) == SOCKET_ERROR) { PrintError("bind() failed."); exit(EXIT_FAILURE); } //開始監聽 // listen(套接字,監聽佇列中允許保持的尚未處理的最大連線數量) // listen僅用在支援連線的套接字上,如SOCK_STREAM型別的套接字 // 如果連線數超過BACKLOG,客戶端將收到WSAECONNREFUSED錯誤 if(listen(sListen, BACKLOG) == SOCKET_ERROR) //FD_SETSIZE { PrintError("sListen() failed."); exit(EXIT_FAILURE); } //初始化 for(int i=0;i<FD_SETSIZE;i++){ arrClientSock[i].fd = -1; } nSelectMaxfd = sListen; //設定select()函式maxfdp引數 //迴圈接收資料 while(true) { struct sockaddr_in remoteAddr; tCurrSysTime = time(NULL); //系統當前時間 //重建fd_set集合 FD_ZERO(&readset); //每次迴圈須重新初始化,否則select不能檢測描述符變化 //將陣列中的fd清理並賦給readset arrClientSockConnAmt = 0; FD_SET(sListen,&readset); //將socket描述符加入檢測集合 nSelectMaxfd = sListen; //設定select()函式maxfdp引數 for(i=0;i< FD_SETSIZE;i++) { if(arrClientSock[i].fd > 0) //從描述符陣列中找到一個還沒用的儲存進去 { //對於空閒時間太長的,可能客戶端已非常規的斷開如斷網,停電之類,將其關閉並從陣列中刪除,DEMO省略 /*if( tCurrSysTime - arrClientSock[i].lastseconds > IDLE_MAXTIME) { close(arrClientSock[i].fd); arrClientSock[i].fd = -1; arrClientSock[i].lastseconds = 0; memset(&arrClientSock[i].addr,0,sizeof(struct sockaddr_in)); }else{*/ FD_SET(arrClientSock[i].fd,&readset); arrClientSockConnAmt ++; //maxfdp if( arrClientSock[i].fd > nSelectMaxfd){ nSelectMaxfd = arrClientSock[i].fd ; } //} } // end if > 0 } //呼叫select //超時則返回0,否則返回發生事件的檔案描述符的個數 nSelectRet = select(nSelectMaxfd+1,&readset,NULL,NULL,NULL); //設定為阻塞狀態 //struct sockaddr_in remoteAddr; //struct timeval timeout={nCheckTimeval,0}; //阻塞式select, 超時時間. timeval{一個是秒數,另一個是毫秒數} //nSelectRet = select(nSelectMaxfd+1,&readset,NULL,NULL,&timeout); //設定select在超時時間內阻塞 if( FD_ISSET(sListen,&readset) ) { printf("select() 返回值 = %d. \n",nSelectRet ); printf("accept() 連線客戶端.\n"); //呼叫accept,連線一個客戶端 sClient = accept(sListen,(struct sockaddr *)&remoteAddr,(socklen_t *)&nAddrlen); if( sClient <= 0) // == INVALID_SOCKET) //-1 { PrintError("accept() failed."); continue; } //描述符陣列已滿 if( arrClientSockConnAmt + 1 > FD_SETSIZE ) { printf("ERROR: 等待連線的客戶端太多!超出處理能力。\n"); continue; } //將連線上的客戶端放入陣列, //後續可以再寫個for,檢查已正常close的並把空閒太長的close掉, //把arrClientSockConnAmt設為實際值,並注意設定nSelectMaxfd的值 for(i=0;i< FD_SETSIZE;i++) { if(arrClientSock[i].fd < 0) //從描述符陣列中找到一個還沒用的儲存進去 { arrClientSock[i].fd = sClient; arrClientSock[i].addr = remoteAddr; arrClientSock[i].lastseconds = time(NULL); printf("連線上的客戶端IP = %s. \n",inet_ntoa(arrClientSock[i].addr.sin_addr) ); arrClientSockConnAmt ++; //maxfdp if( sClient > nSelectMaxfd){ nSelectMaxfd = sClient; } break; } } //如果select()檢測到多個檔案描述符併發時,則繼續while,生成新的socket放入陣列 nSelectRet -= 1; if(nSelectRet <= 0){ continue; //如果沒有新客戶端連線,則繼續迴圈 } } //end if( FD_ISSET(sListen,&readset) ) //把select()函式返回的有發生事件的Socket描述符儲存完後,統一在這做響應處理 for(i = 0;i<arrClientSockConnAmt; i++) { //如果客戶端描述符小於0,則沒有連線 if( arrClientSock[i].fd < 0){ continue; } recvSockfd = arrClientSock[i].fd; if( FD_ISSET(recvSockfd,&readset) ) //檢查可讀 { //接收資料 memset(recvData,0,sizeof(recvData)); //重新清空緩衝區 printf("recv() fd[%d].\n",i); int recvbytes = recv(recvSockfd, recvData, MAXDATASIZE, 0); if( recvbytes == 0) { printf("recv() no data!\n"); close(recvSockfd); FD_CLR(recvSockfd,&readset); arrClientSock[i].fd=-1; arrClientSockConnAmt --; printf("close() \n"); }else if( recvbytes < 0){ PrintError("recv() failed"); close(recvSockfd); FD_CLR(recvSockfd,&readset); arrClientSock[i].fd=-1; arrClientSockConnAmt --; printf("close() \n"); ; //exit(EXIT_FAILURE); //刷屏 }else if(recvbytes > 0){ recvData[recvbytes]='\0'; printf("收到資訊:%s\n",recvData); //傳送資料到客戶端 char sendData[500] ={0}; strcpy(sendData,"Hello client!\n"); strcat(sendData,recvData); send(recvSockfd, sendData, strlen(sendData), 0); //更新一下fd最後響應時間 arrClientSock[i].lastseconds = time(NULL); //如果沒有新客戶端連線,則break for if( (--nSelectRet) <= 0){ break; } } //end if recv } //end if( FD_ISSET(recvSockfd,&readset) ) } //end for } //end while(1) //關閉監聽套接字 close(sListen); exit(EXIT_SUCCESS); }
二。測試 部份
用於測試的程式碼:
/************************************************* Author: xiongchuanliang Description: 通過在不同機器或會話視窗執行測試程式,生成多個執行緒連線Socket伺服器來完成測試 編譯命令: Linux: g++ -o testthread2 testthread2.cpp -m64 -I./common -lpthread ./testthread2 **************************************************/ // 客戶端程式碼 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "initsock.h" #include "common.h" #include <pthread.h> #include <string.h> #include <sys/stat.h> //指定要連線的伺服器ip #define SERVIP "127.0.0.1" #define MAX_THREAD 50 //生成執行緒數 CInitSock initSock; void *TestSocket(void *p); //連線伺服器 int main(int argc, char* argv[]) { pthread_t tpid[MAX_THREAD]; for(int i=0;i< MAX_THREAD - 1;i++) { if( pthread_create(&tpid[i],NULL,&TestSocket,&i) != 0 ) { fprintf(stderr,"Create Thread[%d] Error:%s\n",i,strerror(errno)); exit(EXIT_FAILURE); } //pthread_join(tpid[i],NULL); } sleep(10); exit(EXIT_SUCCESS); } void *TestSocket(void *p) { int ti = *((int *)p); pid_t pid; pid = getpid(); pthread_t tid; tid = pthread_self(); time_t ttm = time(NULL); char testMsg[100] = {0}; snprintf(testMsg,100,"thread id=%lu pid=%u ttm=%d \n",tid, (unsigned int)pid,ttm); //建立套接字 SOCKET sclient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(sclient == INVALID_SOCKET) { PrintError("invalid() failed"); exit(EXIT_FAILURE); } //指定要連線的伺服器地址和埠 struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVPORT); server_addr.sin_addr.s_addr =inet_addr(SERVIP); memset(&(server_addr.sin_zero),0,8); //將套接字連線上伺服器 if( connect(sclient,(struct sockaddr *)&server_addr,sizeof(struct sockaddr) ) == SOCKET_ERROR) { PrintError("connect() failed"); exit(EXIT_FAILURE); } //傳送資料到服務端 send(sclient,testMsg,strlen(testMsg),0); //接收返回的資料 char recvData[MAXDATASIZE] = {0}; int recvbytes = recv(sclient,recvData,MAXDATASIZE,0); if( recvbytes == 0) { printf("thread id=%lu recv() no data!\n",tid); }else if( recvbytes < 0) { PrintError("recv() failed"); exit(EXIT_FAILURE); }else if( recvbytes > 0) { recvData[recvbytes]='\0'; printf("thread id=%lu tm=%d \n服務端返回資訊:%s\n",tid,time(NULL),recvData); } close(sclient); return NULL; }
測試效果圖:
MAIL: [email protected]
BLOG: http://blog.csdn.net/xcl168
相關推薦
網路程式設計(4)select函式實現I/O多路複用伺服器
我按理解整了個基於select模式的單程序多路複用併發伺服器,並寫了個簡單的測試程式測了下,雖然離實用還差得遠,但用來練習select夠用了。 至於如何實現的細節,程式碼註釋算比較清楚,就不多弄了。 一。伺服器部份 單程序併發伺服器程式碼: /*********
I/O多路複用伺服器程式設計
一、實驗目的 理解I/O多路複用技術的原理。 學會編寫基本的單執行緒併發伺服器程式和客戶程式。 二、實驗平臺 ubuntu-8.04作業系統 三、實驗內容 採用I/O多路複用技術實現單執行緒併發伺服器,完成使用一個執行緒處理併發客戶請求的功能。 四、實驗原理 除了可以採用多
poll實現I/O多路複用
函式原型: 函式說明:該函式允許程序指示核心等待多個事件中的任何一個發生,並只在有一個或多個事件發生的時候才。 喚醒它 引數說明: fds:是一個struct pollfd 結構體型別的陣列,用於存放需要檢測其狀態的socket描述符。 每當呼叫這個函式之後,系
Linux網路程式設計---I/O多路複用之select
1.I/O多路複用(IO multiplexing) 我們之前講了I/O多路複用和其他I/O的區別,在這裡,我們再具體討論下I/O多路複用是怎麼工作? I/O 多路複用技術就是為了解決程序或執行緒阻塞到某個 I/O 系統呼叫而出現的技術,使程序不阻塞於某個特定的 I/O 系統呼叫。
嵌入式Linux網路程式設計,I/O多路複用,select()示例,select()客戶端,select()伺服器,單鏈表
文章目錄 1,IO複用select()示例 1.1 select()---net.h 1.2 select()---client.c 1.3 select()---sever.c 1.4 select()---linklist.h
嵌入式Linux網路程式設計,I/O多路複用,阻塞I/O模式,非阻塞I/O模式fcntl()/ioctl(),多路複用I/O select()/pselect()/poll(),訊號驅動I/O
文章目錄 1,I/O模型 2,阻塞I/O 模式 2.1,讀阻塞(以read函式為例) 2.2,寫阻塞 3,非阻塞模式I/O 3.1,非阻塞模式的實現(fcntl()函式、ioctl() 函式)
嵌入式Linux網路程式設計,I/O多路複用,epoll()示例,epoll()客戶端,epoll()伺服器,單鏈表
文章目錄 1,I/O多路複用 epoll()示例 1.1,epoll()---net.h 1.2,epoll()---client.c 1.3,epoll()---sever.c 1.4,epoll()---linklist.h
嵌入式Linux網路程式設計,I/O多路複用,poll()示例,poll()客戶端,poll()伺服器,單鏈表
文章目錄 1,IO複用poll()示例 1.1,poll()---net.h 1.2,poll()---client.c 1.3,poll()---sever.c 1.4,poll()---linklist.h 1.5,p
UNIX網路程式設計-I/O多路複用
目錄 Unix下可用的5種I/O模型 阻塞式I/O模型 非阻塞式I/O模型 I/O複用模型 訊號驅動式I/O模型 非同步I/O模型 各種I/O模型的比較 參考 Unix下可用的5種I/O模型 阻塞式I/O 非阻塞式I/O
淺談網路I/O多路複用模型 select & poll & epoll
我們首先需要知道select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,
Socket網路程式設計_之I/O多路複用
1. IO多路複用: 每一次網路通訊都是一個Socket的I/O流,對於伺服器而言,有兩種方法 1.傳統的多程序併發模型(每進來一個新的I/O流會分配一個新的程序管理。) 2.方法二就是I/O的多路複用
Linux網路程式設計---I/O多路複用之epoll
/* TCP伺服器 用法:./server port */ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string
Java網路程式設計與NIO詳解2:JAVA NIO 一步步構建I/O多路複用的請求模型
微信公眾號【黃小斜】作者是螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、SSM全家桶、MySQL、分散式、中介軟體、微服務,同時也懂點投資理財,堅持學習和寫作,相信終身學習的力量!關注公眾號後回覆”架構師“即可領取 Java基礎、進階、專案和架構師等免費學習資料,更有資料
I/O多路複用之select、poll、epoll
很早之前有寫過篇IO多路複用的文章:https://www.cnblogs.com/klcf0220/archive/2013/05/14/3077003.html 參考連結:https://segmentfault.com/a/1190000003063859 select,poll,epoll都是IO多路
java併發程式設計之IO基礎入門之I/O多路複用技術
在I/O程式設計過程中,當需要同時處理多個客戶端接入請求時,可以利用多執行緒或者I/O多路複用技術進行處理。I/O多路複用技術通過把多個I/O的阻塞複用到同一個select的阻塞上,從而使得系統在單執行緒的情況下可以同時處理多個客戶端請求。與傳統的多執行緒/多程序模型比,
從網路I/O模型到Netty,先深入瞭解下I/O多路複用
微信搜尋【阿丸筆記】,關注Java/MySQL/中介軟體各系列原創實戰筆記,乾貨滿滿。 本文是Netty系列第3篇 上一篇文章我們瞭解了Unix標準的5種網路I/O模型,知道了它們的核心區別與各自的優缺點。尤其是I/O多路複用模型,在高併發場景下,有著非常好的優勢。而Netty也採用了I
I/O 多路複用
I/O型別: 接下來我們將介紹幾種常見的I/O模型及其區別 阻塞I/O:blocking I/O(如果沒有資訊,則阻塞)  
【Linux】I/O多路複用
五種IO模型 阻塞IO(等待魚上鉤) 在核心將資料準備好之前,系統呼叫會一直等待,所有的套接字,預設是阻塞模式。 等待,拷貝資料到buf中,(等待的時間長) 非阻塞IO(定期檢視是否有魚上鉤) 如果核心還未將資料
I/O多路複用技術(multiplexing)
作者:知乎使用者 連結:https://www.zhihu.com/question/28594409/answer/52835876 來源:知乎 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。 下面舉一個例子,模擬一個tcp伺服器處理30個客戶soc
IO基礎入門之I/O多路複用技術
在I/O程式設計過程中,當需要同時處理多個客戶端接入請求時,可以利用多執行緒或者I/O多路複用技術進行處理。I/O多路複用技術通過把多個I/O的阻塞複用到同一個select的阻塞上,從而使得系統在單執行緒的情況下可以同時處理多個客戶端請求。與傳統的多執行緒/多程序模型比,I