朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
? ? ? ? 在《樸素、Select、Poll和Epoll網(wǎng)絡(luò)編程模型實現(xiàn)和分析——樸素模型》中我們分析了樸素模型的一個缺陷——一次只能處理一個連接。本文介紹的Select模型則可以解決這個問題。(轉(zhuǎn)載請指明出于breaksoftware的csdn博客)
? ? ? ? 和樸素模型一樣,我們首先要創(chuàng)建一個監(jiān)聽socket,然后調(diào)用listen去監(jiān)聽服務(wù)器端口。不同的是,我們要對make_socket方法傳遞1,因為我們要創(chuàng)建一個異步的socket。
listen_sock = make_socket(1);if (listen(listen_sock, SOMAXCONN) < 0) {perror("listen error");exit(EXIT_FAILURE);}
? ? ? ? 下一步我們需要清空Select模型使用的fd_set結(jié)構(gòu)體對象,并把監(jiān)聽socket設(shè)置進去
FD_ZERO(&active_fd_set);FD_SET(listen_sock, &active_fd_set);
? ? ? ??active_fd_set用于記錄活動的socket信息。之后我們使用到的read_fd_set則是其一個拷貝,因為我們只關(guān)心讀行為
fd_set active_fd_set, read_fd_set;
? ? ? ? 和樸素模型類似,我們也需要使用一個死循環(huán)讓服務(wù)器不要停止
/* Initialize the set of active sockets. */while (1) {timeout.tv_sec = 0;timeout.tv_usec = 500;/* Service all the sockets with input pending. */read_fd_set = active_fd_set;switch(select(FD_SETSIZE, &read_fd_set, NULL, NULL, &timeout)) {case -1 : {perror("select error\n");exit(EXIT_FAILURE);}break;case 0 : {//perror("select timeout\n");}break;default: {
? ? ? ? select函數(shù)第一個參數(shù)我們傳遞了FD_SETSIZE,它在我的系統(tǒng)上是1024,它代表需要關(guān)注的socket的最大個數(shù)。第二參數(shù)是用于記錄需要關(guān)注的發(fā)生讀事件的fd_set對象。我們讓select函數(shù)按異步方式執(zhí)行,故最后一個參數(shù)設(shè)置為500微秒的超時時間。整個select函數(shù)意思是我們需要等待socket發(fā)生可讀事件,如果等待時間超過超時設(shè)置,則函數(shù)返回0,如果出錯則返回-1,如果等待到事件則返回其他值。
default: {for (index = 0; index < FD_SETSIZE; ++index) {if (FD_ISSET(index, &read_fd_set)) {if (listen_sock == index) {/* Connection request on original socket. */int new_sock;new_sock = accept(listen_sock, NULL, NULL);if (new_sock < 0) {perror("accept error");exit(EXIT_FAILURE);}request_add(1);//set_block_filedes_timeout(new_sock);FD_SET(new_sock, &active_fd_set);} else {if (0 == server_read(index)) {server_write(index);}close(index);FD_CLR(index, &active_fd_set);}}}}}}return 0;
}
? ? ? ? default中才是我們程序的重點。
? ? ? ? 我們使用一個for循環(huán)遍歷每個socket。如果該socket通過FD_ISSET宏判斷不處于我們關(guān)注的可讀事件fd_set中,則忽略它。
? ? ? ? 如果處在可讀fd_set中,則看看其是否是監(jiān)聽socket。
? ? ? ? 如果是監(jiān)聽socket,則使用accpet方法獲取接入的socket。并使用request_add讓請求數(shù)量加一。還要使用FD_SET宏將該socket加入到活動狀態(tài)的fd_set中。之后該活動狀態(tài)的fd_set將被賦值給需要關(guān)注可讀事件的fd_set中。
? ? ? ? 如果不是監(jiān)聽socket,則是接入的socket。于是我們調(diào)用《樸素、Select、Poll和Epoll網(wǎng)絡(luò)編程模型實現(xiàn)和分析——樸素模型》一文中介紹的server_read和server_write方法讀取內(nèi)容并回包。最后我們還要關(guān)閉socket,并使用FD_CLR宏將該socket從活動狀態(tài)的fd_set中去掉。之后的select函數(shù)將不會在關(guān)注該socket了。
? ? ? ? 整個過程非常簡單。但是這其中卻包含了很多值得思考的問題。
? ? ? ? 首先我拋出一個問題,我在default中使用了一個從0到FD_SETSIZE的遍歷行為。并且將遍歷的游標(biāo)——index作為socket去操作——使用server_read和server_write去讀取。于是問題就來了,使用make_socket創(chuàng)建的socket值和使用accept接收到的socket的值怎么和游標(biāo)產(chǎn)生關(guān)聯(lián)?代碼中似乎沒有任何讓它們產(chǎn)生關(guān)聯(lián)的邏輯,而且它們的關(guān)系是嚴(yán)格的“相等”的關(guān)系!那么只有一個假設(shè),就是make_socket和accept返回的socket值在FD_SETSIZE和0之間。但是目前我沒有找到文檔對這個問題進行說明,而我也沒深入研究這兩個函數(shù)考證到其值就是在這個范圍之內(nèi),那么為什么我還要這么去用呢?
? ? ? ? 我們先記下這個問題,深入到linux的源碼中取解釋這個使用的正確性。
? ? ? ? 我們先看下fd_set的定義
/* The fd_set member is required to be an array of longs. */
typedef long int __fd_mask;/* Some versions of <linux/posix_types.h> define this macros. */
#undef __NFDBITS
/* It's easier to assume 8-bit bytes than to get CHAR_BIT. */
#define __NFDBITS (8 * (int) sizeof (__fd_mask))
#define __FD_ELT(d) ((d) / __NFDBITS)
#define __FD_MASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS))/* fd_set for select and pselect. */
typedef struct{/* XPG4.2 requires this member name. Otherwise avoid the namefrom the global namespace. */
#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif} fd_set;/* Maximum number of file descriptors in `fd_set'. */
#define FD_SETSIZE __FD_SETSIZE
? ? ? ? 以上我是在我ubuntu系統(tǒng)的/usr/include/x86_64-linux-gnu/sys/select.h文件中找到的定義。我們看到fd_set的主體就是一個long int型數(shù)組__fds_bits。該數(shù)組的個數(shù)是兩個數(shù)的商。被除數(shù)__FD_SETSIZE就是我們程序中使用的FD_SETSIZE,也就是1024。除數(shù)__NFDBITS是64。于是fd_set中數(shù)組元素的個數(shù)是1024/64=16。注意一下這個值是16,而我們程序中關(guān)注的socket的最大個數(shù)是FD_SETSIZE——1024,這是為什么?其實這就是該結(jié)構(gòu)設(shè)計的一個精妙之處。fd_set的__fds_bits是一個16個元素的long int型數(shù)組,其總長度就是16*64=1024位。于是可以使用每一位表示一個socket。
? ? ? ? 我們到/usr/include/x86_64-linux-gnu/bits/select.h 文件中看看linux是如何讓socket和這個空間中每一位進行對應(yīng)的。我們查看FD_SET宏
#define __FD_SET(d, set) \((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))
? ? ? ??__FDS_BITS宏定義在fd_set定義中。它就是返回fd_set的__fds_bits數(shù)組首地址。數(shù)組的游標(biāo)是通過__FD_ELT對socket進行處理的結(jié)果。__FD_ELT在上面我們已經(jīng)見過,它是對socket值除以__NFBITS——64的值。通過游標(biāo)我們?nèi)〉綌?shù)組元素值之后,我們再用其與__FD_MASK對socket進行操作的值進行或操作。__FD_MASK的定義也在上面給出,它是將socket的值與__NFBITS——64相除,取得余數(shù),然后讓1左移該余數(shù)次。這樣我們就將該socket映射到fd_set內(nèi)存的一位中。我們知道,只要在知道除數(shù)、商和余數(shù)的情況下,可以很方便的推算出被除數(shù)是多少。可以說linux內(nèi)核對這塊的設(shè)計真是做到了極致,不浪費一點點空間。
? ? ? ? 有了上面的認識,我們就知道select模型最大只能支持FD_SETSIZE個數(shù)的socket,而且socket的值也只能在FD_SETSIZE之內(nèi)。如果socket()或accept()函數(shù)返回的socket值大于FD_SETSIZE,則select模型將出現(xiàn)錯誤——上面的計算將溢出。基于這種反向推理,我們可以放心大膽的使用0到FD_SETSIZE的值去當(dāng)socket的值去計算。我看網(wǎng)上有很多select例子需要使用一個數(shù)組去維護接入的socket,如果在不考慮效率的前提下是不必要的。但是如果你追求極致的Select模型性能,還是建議使用一個數(shù)組去維護Socket,這樣不至于出現(xiàn)大量的浪費操作。這塊分析我們將在后文中給出。
? ? ? ? 這兒再多言一句,正是因為這種位操作,我們才需要在使用fd_set之前調(diào)用FD_ZERO去清空所有空間
# if __WORDSIZE == 64
# define __FD_ZERO_STOS "stosq"
# else
# define __FD_ZERO_STOS "stosl"
# endif# define __FD_ZERO(fdsp) \do { \int __d0, __d1; \__asm__ __volatile__ ("cld; rep; " __FD_ZERO_STOS \: "=c" (__d0), "=D" (__d1) \: "a" (0), "0" (sizeof (fd_set) \/ sizeof (__fd_mask)), \"1" (&__FDS_BITS (fdsp)[0]) \: "memory"); \} while (0)
? ? ? ? 如果socket不再需要監(jiān)測,則我們使用__FD_CLR在fd_set中去除其對應(yīng)的位
#define __FD_CLR(d, set) \((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))
? ? ? ? 我們再來看下Select模型的處理能力。我們采用和《樸素、Select、Poll和Epoll網(wǎng)絡(luò)編程模型實現(xiàn)和分析——樸素模型》一文中相同的環(huán)境和壓力,看下服務(wù)器的數(shù)據(jù)輸出
? ? ? ? 再看下客戶端的輸出
? ? ? ? 可見當(dāng)前環(huán)境下,select模型的處理能力大概是每秒7000多連接。(和下一章介紹的Poll模型差距不大,而且如果使用數(shù)組維護Socket還可以提高性能)
總結(jié)
以上是生活随笔為你收集整理的朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 朴素、Select、Poll和Epoll
- 下一篇: 朴素、Select、Poll和Epoll