淺析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客戶端跟連一個客戶端一個麻溜溜的。測試代碼如下: 

 

 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函數。

同時,客戶端在send的時候一次發2k數據,在server接收一次1k的,第一次沒取完,select會再次檢測到該fd可讀,再收一次,正好2k,select才不會檢測到該fd可讀。這個例子是一個簡單的非阻塞(NIO)的例子,難點就是對於半包問題要處理好。很多時候我們接收到的數據要完整了才行進行decode。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM