日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Socket编程实践(8) --Select-I/O复用

發(fā)布時間:2025/3/17 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Socket编程实践(8) --Select-I/O复用 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

五種I/O模型介紹

(1)阻塞I/O[默認]


? ?當上層應用App調用recv系統(tǒng)調用時,如果對等方?jīng)]有發(fā)送數(shù)據(jù)(Linux內核緩沖區(qū)中沒有數(shù)據(jù)),上層應用Application1將阻塞;當對等方發(fā)送了數(shù)據(jù),Linux內核recv端緩沖區(qū)數(shù)據(jù)到達,內核會把數(shù)據(jù)copy給用戶空間。然后上層應用App解除阻塞,執(zhí)行下一步操作。

?

(2)非阻塞I/O[少用]


? ?上層應用App將套接字設置成非阻塞模式,?然后循環(huán)調用recv函數(shù),接受數(shù)據(jù)。若緩沖區(qū)沒有數(shù)據(jù),上層應用不會阻塞,recv返回值為-1,錯誤碼是EWOULDBLOCK。

? ?上層應用程序不斷輪詢有沒有數(shù)據(jù)到來。造成上層應用忙等待。大量消耗CPU。因此非阻塞模式很少直接用。應用范圍小,一般和IO復用配合使用。

?

(3)I/O多路復用[重點]


? ?上層應用App調用select等其他IO復用系統(tǒng)調用(該機制由Linux內核支持,避免了App忙等待),進行輪詢文件描述符的狀態(tài)變化;?當select管理的文件描述符沒有數(shù)據(jù)(或者狀態(tài)沒有變化時),上層應用也會阻塞。

? ?好處是:select機制可以管理多個文件描述符;?可以將select看成一個管理者,用select來管理多個IO,?一旦檢測到的一個IO或者多個IO,有我們感興事件發(fā)生時,select函數(shù)將返回,返回值為檢測到的事件個數(shù)。進而可以利用select相關API函數(shù),操作具體事件。

? ?select函數(shù)可以設置等待時間,避免了上層應用App長期僵死。

? ?和阻塞IO模型相比,select?I/O復用模型相當于提前阻塞了。等到有數(shù)據(jù)到來時,再調用recv就不會發(fā)生阻塞。

?

(4)信號驅動I/O[并不常用]


? ?上層應用App建立SIGIO信號處理程序。當緩沖區(qū)有數(shù)據(jù)到來,內核會發(fā)送信號告訴上層應用App;?當上層應用App接收到信號后,調用recv函數(shù),因緩沖區(qū)有數(shù)據(jù),recv函數(shù)一般不會阻塞。

? ?這種用于模型用的比較少,屬于典型的“拉模式(上層應用被動的去Linux內核空間中拉數(shù)據(jù))”。即:上層應用App,需要調用recv函數(shù)把數(shù)據(jù)拉進來,會有時間延遲,我們無法避免在延遲時,又有新的信號的產(chǎn)生。

?

(5)異步I/O[并不常用]


? ?上層應用App調用aio_read函數(shù),同時提交一個應用層的緩沖區(qū)buf;調用完畢后,不會阻塞。上層應用程序App可以繼續(xù)其他任務;?當TCP/IP協(xié)議緩沖區(qū)有數(shù)據(jù)時,Linux主動的把內核數(shù)據(jù)copy到用戶空間。然后再給上層應用App發(fā)送信號;告訴App數(shù)據(jù)到來,需要處理!

? ?異步IO屬于典型的“推模式”,?是效率最高的一種模式,上層應用程序App有異步處理的能力(在Linux內核的支持下,處理其他任務的同時,也可支持IO通訊,?與Windows平臺下的完成端口作用類似IOCP)。

?

Select-I/O復用

#include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

? ? select實現(xiàn)的是一個管理者的功能:?用select來管理多個IO,?一旦其中的一個IO或者多個IO檢測到我們所感興趣的事件,?select就返回,?返回值就是檢測到的事件個數(shù),?并且由第2~4個參數(shù)返回那些IO發(fā)送了事件,?這樣我們就可以遍歷這些事件,?進而處理這些事件;

參數(shù):

nfds: is?the?highest-numbered?file?descriptor?in?any?of?the?three?sets,plus?1[讀,寫,異常集合中的最大文件描述符+1].

fd_set[四個宏用來對fd_set進行操作]

???????FD_CLR(int?fd,?fd_set?*set);

???????FD_ISSET(int?fd,?fd_set?*set);

???????FD_SET(int?fd,?fd_set?*set);

???????FD_ZERO(fd_set?*set);

timeout[從調用開始到select返回前,會經(jīng)歷的最大等待時間,?注意此處是指的是相對時間]

//timeval結構: struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */ }; //一些調用使用3個空的set, n為0, 一個非空的timeout來達到較為精確的sleep.

Linux中,?select函數(shù)改變了timeout值,用來指示還剩下的時間,但很多實現(xiàn)并不改timeout。

為了較好的可移植性,timeout在循環(huán)中一般常被重新賦初值。

?

Timeout取值:

? ??timeout==?NULL

? ??? ??無限等待,被信號打斷時返回-1,?errno?設置成?EINTR

? ??timeout->tv_sec?==?0?&&?tvptr->tv_usec?==?0

? ??? ??不等待立即返回

? ??timeout->tv_sec?!=?0?||?tvptr->tv_usec?!=?0

? ??? ??等待特定時間長度,?超時返回0

?

返回值:

??On?success,?select()?and?pselect()?return?the?number?of??file??descriptors??contained??in??

the??three??returned?descriptor?sets?(that?is,?the?total?number?of?bits?that?are??set??in??

readfds,??writefds,??exceptfds)?which??may??be??zero?if?the?timeout?expires?before?anything?

interesting?happens.??On?error,?-1?is?returned,?and?errno?is?set?appropriately;?the?sets??

and??timeout??become??undefined,?so?do?not?rely?on?their?contents?after?an?error.

? ?如果成功,返回所有sets中描述符的個數(shù);如果超時,返回0;如果出錯,返回-1.

?

讀,?寫,?異常事件發(fā)生條件

可讀:

可寫:

異常:

套接口緩沖區(qū)有數(shù)據(jù)可讀(連接的對等方發(fā)送數(shù)據(jù)過來,?填充了本地套接口緩沖區(qū),?所以導致套接口緩沖區(qū)有數(shù)據(jù)可讀);

套接口發(fā)送緩沖區(qū)有空間容納數(shù)據(jù)(因為大部分時間發(fā)送緩沖區(qū)是未滿的,?因此我們一般不關心這個事件);

套接口存在帶外數(shù)據(jù);

?

連接的讀一半(對端)關閉(對方調用了close),?即接收到FIN段,?讀操作將返回0;

連接的寫一半關閉.?即收到RST段之后,?再次調用write操作;

?

如果是監(jiān)聽套接口,?已完成隊列不為空時;

套接口發(fā)生了一個錯誤待處理,?錯誤可以通過getsockopt指定SO_ERROR選項來獲取;

?

套接口發(fā)生了一個錯誤等待處理,?錯誤可以通過getsockopt指定SO_ERROR選項來獲取;

?

/**示例1: 用select來改進echo回聲服務器的client端的echoClient函數(shù) 使得可以在單進程的情況下同時監(jiān)聽多個文件描述符; **/ void echoClient(int sockfd) {char buf[512];fd_set rset;//確保標準輸入不會被重定向int fd_stdin = fileno(stdin);int maxfd = fd_stdin > sockfd ? fd_stdin : sockfd;while (true){FD_ZERO(&rset);FD_SET(fd_stdin, &rset);FD_SET(sockfd, &rset);int nReady = select(maxfd+1, &rset, NULL, NULL, NULL);if (nReady == -1)err_exit("select error");else if (nReady == 0)continue;/** nReady > 0: 檢測到了可讀事件 **/if (FD_ISSET(fd_stdin, &rset)){memset(buf, 0, sizeof(buf));if (fgets(buf, sizeof(buf), stdin) == NULL)break;if (writen(sockfd, buf, strlen(buf)) == -1)err_exit("write socket error");}if (FD_ISSET(sockfd, &rset)){memset(buf, 0, sizeof(buf));int readBytes = readline(sockfd, buf, sizeof(buf));if (readBytes == 0){cerr << "server connect closed..." << endl;exit(EXIT_FAILURE);}else if (readBytes == -1)err_exit("read-line socket error");cout << buf;}} } /**示例2: 用select來改進echo回聲服務器的server端的接受連接與處理連接部分的代碼: 使得可以在單進程的情況下處理多客戶連接, 對于單核的CPU來說, 單進程使用select處理連接與監(jiān)聽套接字其效率不一定就會比多進程/多線程性能差;**/struct sockaddr_in clientAddr;socklen_t addrLen;int maxfd = listenfd;fd_set rset;fd_set allset;FD_ZERO(&rset);FD_ZERO(&allset);FD_SET(listenfd, &allset);//用于保存已連接的客戶端套接字int client[FD_SETSIZE];for (int i = 0; i < FD_SETSIZE; ++i)client[i] = -1;int maxi = 0; //用于保存最大的不空閑的位置, 用于select返回之后遍歷數(shù)組while (true){rset = allset;int nReady = select(maxfd+1, &rset, NULL, NULL, NULL);if (nReady == -1){if (errno == EINTR)continue;err_exit("select error");}//nReady == 0表示超時, 但是此處是一定不會發(fā)生的else if (nReady == 0)continue;if (FD_ISSET(listenfd, &rset)){addrLen = sizeof(clientAddr);int connfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);if (connfd == -1)err_exit("accept error");int i;for (i = 0; i < FD_SETSIZE; ++i){if (client[i] < 0){client[i] = connfd;if (i > maxi)maxi = i;break;}}if (i == FD_SETSIZE){cerr << "too many clients" << endl;exit(EXIT_FAILURE);}//打印客戶IP地址與端口號cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)<< ", " << ntohs(clientAddr.sin_port) << endl;//將連接套接口放入allset, 并更新maxfdFD_SET(connfd, &allset);if (connfd > maxfd)maxfd = connfd;if (--nReady <= 0)continue;}/**如果是已連接套接口發(fā)生了可讀事件**/for (int i = 0; i <= maxi; ++i)if ((client[i] != -1) && FD_ISSET(client[i], &rset)){char buf[512] = {0};int readBytes = readline(client[i], buf, sizeof(buf));if (readBytes == -1)err_exit("readline error");else if (readBytes == 0){cerr << "client connect closed..." << endl;FD_CLR(client[i], &allset);close(client[i]);client[i] = -1;}//注意此處: Server從Client獲取數(shù)據(jù)之后并沒有立即回射回去,// 而是等待四秒鐘之后再進行回射sleep(4);cout << buf;if (writen(client[i], buf, readBytes) == -1)err_exit("writen error");if (--nReady <= 0)break;}}

完整源代碼請參照:

http://download.csdn.net/detail/hanqing280441589/8486517

總結

以上是生活随笔為你收集整理的Socket编程实践(8) --Select-I/O复用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。