淺析tcp中read阻塞

       最近學習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。函數

相關文章
相關標籤/搜索