我用select做多路復用踩到的坑
bool SocketAction(int fd, const char* buf, size_t len, uint64_t milli_expire) { struct timeval tv; tv.tv_sec = milli_expire / 1000; tv.tv_usec = (milli_expire % 1000) * 1000; fd_set rd_set, wt_set; FD_ZERO(&rd_set); FD_ZERO(&wt_set); FD_SET(fd, &rd_set); FD_SET(fd, &wt_set); int ret = 0; while (true) { ret = select(fd+1, &rd_set, &wt_set, nullptr, &tv); if (ret < 0 && errno == EINTR) continue; break; } if(FD_ISSET(fd, &rd_set)) { char rd_buf[1024] = {0}; ret = read(fd, rd_buf, sizeof(rd_buf)); if(ret < 0) return false; printf("%s\n", rd_buf); } else if(FD_ISSET(fd, &wt_set)) { ret = send(fd, buf, len, 0); if(ret < 0) return false; } return true; }
上面的代碼著實非常easy,僅僅是針對某一個socket fd進行讀寫操作,從邏輯上來說應該是沒有不論什麽問題的。然而這個在實際的使用過程中確實會出現故障。我們程序中封裝了socket的操作,如上代碼的方式使用了select。
在出現大量的socket連接時,會出現宕機現象,而且宕機生成的core文件的堆棧也莫名其妙的被破壞了。
遇到這個情況時。我直接被震驚了,由於細致檢查了代碼確實沒有發現不論什麽問題。為什麽每次都是連接數達到快1500的時候就出現宕機呢?
剛開始以為是其它的邏輯出了問題。於是細致檢查了其它的邏輯確實也沒有發現存在不論什麽問題,而且從堆棧被破壞的情況上分析,也懷疑是某個使用中出現了數組越界訪問引發的。但在整個邏輯中使用的都是stl裏的容器,沒有申請數組,出現讀寫越界著實無法理解。
此後僅僅能懷疑底層封裝的問題。在一步步嘗試凝視代碼的過程中。發現select使用的地方出現了問題。那麽問題到底在哪裏呢?
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds is the highest-numbered file descriptor in any of the three sets, plus 1.
在select的使用manual中,select的定義和nfds的說明如上所看到的。也僅僅是說明了select的nfds是最大的文件描寫敘述符+1。在實際的使用過程中也確實有這麽使用。好像也確實沒有問題。然而,不止這麽簡單。select的文件描寫敘述符的最大值實際是有一個潛在規則的。那就是select的最大文件描寫敘述符最大是1024。該值在centor os系統中。定義
78 /* Maximum number of file descriptors in `fd_set‘. */
79 #define FD_SETSIZE __FD_SETSIZE
而__FD_SETSIZE則定義在:/usr/include/bits/typesizes.h 中,例如以下:
62 /* Number of descriptors that can fit in an `fd_set‘. */
63 #define __FD_SETSIZE 1024
這下能夠理解了吧。在socket大於1024時。文件描寫敘述符自然就大於1024,則在使用select時訪問的大小就實際超越了系統中對於select的支持。因此出現讀寫越界,造成程序的宕機。
這次所踩的坑很隱晦,但也說明了一個問題。在使用系統接口時須要謹慎。盡管可能用法並沒有錯誤,但也要在使用的時候多註意其“潛規則”。
我用select做多路復用踩到的坑