最近學習route組件,瞭解了些關於tcp通訊中I/O複用的知識。好比:select,poll,epoll。目前系統主要是用select。原本覺得select是個好東西,解決了單進程單線程的server能夠鏈接多個客戶端的問題。後來,同事跟我說read函數是阻塞的,那麼鏈接創建後,server會阻塞在read處,其餘鏈接就無法正常工做了。而後這個問題就一直困擾着。想起了以前在知乎上有個問題是:怎麼設計tcp鏈接?有個點贊不少的是建多線程,一個線程一個鏈接。可是,也有很多人批判這個設計,說這樣太費資源,指出I/O多路複用中使用相似於select在一個線程中能夠實現鏈接多個客戶端。當時也是沒想明白,並且公司route組件(老版本)的設計是一個線程一個鏈接,apache也是一個進程一個鏈接,壞處就是鏈接數量不多,畢竟進程切換是耗cpu的。後來就查閱資料,而後經過代碼測試,read的阻塞能夠不會干擾其餘鏈接的,一個server連N客戶端跟連一個客戶端一個麻溜溜的。測試代碼以下: apache
1 for (;;) { 2 memset(szBuf, 0, sizeof(szBuf)); 3 FD_ZERO(&fset); 4 FD_SET(fd, &fset); 5 tv.tv_sec = 5; 6 tv.tv_usec = 0; 7 8 for (int i = 0; i < BACKLOG; i++) { 9 if (fd_A[i] != 0) 10 FD_SET(fd_A[i], &fset); 11 } 12 13 ret = select(maxfd+1, &fset, NULL, NULL, &tv); 14 15 if (ret < 0) { 16 printf("select調用發生錯誤\n"); 17 break; 18 } 19 else if (ret == 0) { 20 printf("select timeout\n"); 21 continue; 22 } 23 else { 24 printf("select normal\n"); 25 } 26 27 for (int i = 0; i < BACKLOG; i++) { 28 if (fd_A[i] && FD_ISSET(fd_A[i], &fset)) { 29 printf("recv before\n"); 30 if ((ret = recv(fd_A[i], szBuf, sizeof(szBuf), 0)) == 0) { 31 close(fd_A[i]); 32 FD_CLR(fd_A[i], &fset); 33 fd_A[i] = 0; 34 conn_amount--; 35 } 36 else { 37 printf("fd_A[%d]:%s", i, szBuf); 38 } 39 } 40 } 41 42 if (FD_ISSET(fd, &fset)) { 43 newfd = accept(fd, (struct sockaddr *)&cli_addr, &cli_len); 44 if (newfd <= 0) { 45 printf("accept出錯\n"); 46 continue; 47 } 48 else 49 printf("accept normal\n"); 50 。。。
當客戶端connect鏈接上的時候,會輸出"select normal" "accept normal",沒有走到read/recv這塊;若是5秒內客戶端沒其餘操做,server就會在select處超時(select的超時時間設置的是5秒)。接着客戶端調用send,而後代碼會走到select->read這塊,並無進去accept。由於在調用accept,recv/read以前我用了FD_ISSET來判斷。多線程
fd_set是一組文件描述符(fd)的集合,它用一位來表示一個fd。至於fd有多大,操做系統定義了常量FD_SETSIZE。在好久 之前是32,如今通常是1024。select函數用於檢查fd_set集合中是否有可讀的,同時也會更新fd_set集合。FD_ISSET用於測試指定的文件描述符是否在該集合中。假設如今客戶端1是成功鏈接的,若是客戶端2發起鏈接,那麼select後客戶端1對應fd使用FD_ISSET後返回值是false的,那麼就不去調用recv/read函數。若是客戶端1發送數據過來,select檢測到後,使用FD_ISSET判斷鏈接1返回true,能夠用recv/read不會阻塞;使用FD_ISSET判斷鏈接2的返回是false的,不去調用recv/read函數。tcp
同時,客戶端在send的時候一次發2k數據,在server接收一次1k的,第一次沒取完,select會再次檢測到該fd可讀,再收一次,正好2k,select纔不會檢測到該fd可讀。這個例子是一個簡單的非阻塞(NIO)的例子,難點就是對於半包問題要處理好。不少時候咱們接收到的數據要完整了才行進行decode。函數