首页 > 代码库 > lwip 之 select 暨 keepalive 笔记
lwip 之 select 暨 keepalive 笔记
最近在使用國人自己的實時作業系統rt_thread,在stm32f103上寫一些應用。其中使用到了網絡編程。
當仁不讓,最基本的select()逃不掉;setsockopt()也逃不掉。下面就把自己的使用情況記錄如下。
先說說select()函數。
其實它不限於網絡平台,主要是把永遠阻塞變成某個時間段的等待,所謂超時機制是也---其實,更好理解的是:把無期徒刑變有期徒刑。它是這樣的:它會去一個大的集合裡面檢測這個集合裡面元素的操作性質,一旦檢測出了某個或者某幾個元素操作性質有變化了,就會馬上返回一個值,這個值代表集合中操作屬性有變化的元素個數。然後用戶就根據自己的心情,按照可操作的性質,就操作這些元素即可。
那麼,這個集合是什麼呢?類似一個數組。數組的每一個格格裝一個元素。
那麼元素是什麼呢? 文件描述符:比如 打開一個文本文件,會產生一個描述符;打開一個uart,也會產生一個描述符;打開一個socket套接字,那也是一個描述符... 這些描述符就是這些元素,可多可少.. 傳說在1024個左右。是否為真,還得查下書。
那麼這些元素,或者說這些文件描述符的操作屬性是什麼呢? 嗯,linux下似乎只有這三個rwx,或者421.而在這裡,這些文件描述符的屬性包括: 可讀屬性 <"r">、可寫屬性<"w">、異常屬性<不好意思,不是 "x" 屬性了>。
那麼,這些屬性什麼時候會發生變動呢? 比如,串口uart在等待數據的時候,描述符的讀狀態為0. 突然,對端設備拋一串數據過來了,於是這個時候,描述符的讀狀態就變成 1了; tcp server和一個client連接好以後,然後等待client端的數據,這個時候server的套接字狀態讀狀態為0,當client突發扔一串數據過來了,這樣一沖,server的套接字的讀狀態就變為1了。<以上語言僅僅是假設,具體文件描述符的狀態是怎麼設置的,這個要看linux kernel中是怎麼實現的>。
於是乎,讀狀態從0到1這樣一個狀態的變化,就引起了select()的注意---在後面,它會把這個情況告訴給程序員,程序員自己去處理了:讀屬性發生了變化,那就讀數據嘛,寫狀態發生了變化,那就寫數據嘛,表廢話。但有一點需要存疑:當select()發現一個描述符的讀或者寫狀態屬性發生了變化后,是馬上就返回,還是會繼續監測整個集合中的元素,當監測完了后,再繼續返回呢?<不是等到超時才返回>
好吧,該說說程序員的心情問題了。什麼樣的心情呢? 我只關心uart數據是否可讀了,不關心uart數據是否可寫;我只關心我的套接字是否能往對端寫東西了,才不管是否有數據可讀了.. 或者,我不放心打開的這個文本文件會不會鬼混去了<異常>.. 或者,我精力無限,這些通通都要關心一把.. 也行,沒問題,select已經考慮好了。
既然該說的都說了,那是否就該立刻用select()來整一段高大上的代碼?別急,還有一個致命的一點:
那就是select()它也是有性格的---每執行一次,它的某些參數都要重置一次,否則,它就會罷工的<當然,在一個菜鳥的我手裡,曾經它罷工的平率是1,不存在 小於1的情況,這樣是這篇筆記產生的原因>。
好吧,其他方面的,網絡資料無限,書上也說的非常明白了,而且注意點,也說的非常明白了。不再多說了,下面直接上一小段代碼,主要是針對參數重置部分的while(1)循環步驟:
1 int client_hearbet(void) 2 { 3 int retval = -1, maxfdp = -1, heart_count = 0; 4 struct timeval timeout; 5 fd_set readset; 6 /**/ 7 //...... 8 maxfdp = client_sock + 1; 9 FD_ZERO(&readset);10 FD_SET(client_sock, &readset);11 while(1) {12 //......13 timeout.tv_sec = 20;14 timeout.tv_usec = 0;15 maxfdp = client_sock + 1;16 FD_ZERO(&readset);17 FD_SET(client_sock, &readset); 18 retval = select(maxfdp, &readset, NULL, NULL, &timeout);19 //....20 else if( !retval ) {21 //....22 FD_CLR(client_sock,&readset);23 continue ;24 }25 else {26 if(FD_ISSET(client_sock, &readset)) {27 /*recv*/ 28 29 recv_bytes = recv(client_sock, heart_buff, LEN, 0);30 //.....31 FD_CLR(client_sock,&readset);32 //.....33 }34 //...... 35 FD_CLR(client_sock,&readset);36 }37 }38 }
弄的是網絡編程的吧,其實也就是lwip socket編程了。其中注意13 ~17 行,是在while(1)循環裡面;注意第15行,是當前最大描述符 + 1 --> 這個就代表當前集合的大小了吧。
嗯,曠日持久的select()罷工問題就這麼解決了。并寫下了這部分筆記,以供需要的朋友參考<高手飄過了~>
--------------------------------------------------------------------------------
下面來說說使用lwip的setsockopt()的問題。
lwip也是一個tcp/ip的協議棧,不過稍微悲慘的是,它沒有pc上的tcp/ip那樣細膩與完整---畢竟是一個經過壓縮,要跑在嵌入式設備上的tcp/ip版本。鑒於此,某些地方,就需要自己動手,豐衣足食了。
下面直接寫結果吧:
要啟用端口和地址複用功能,一般的都是setsockopt()設置 SO_REUSEADDR 選項;要想在處於連接狀態下,能馬上感覺有人把網線給拔掉了,一般會使用setsockopt()設置keepalive選項。當然,這在lwip裡面也已經支持了。
看看怎麼個豐衣足食法:
1 在 opt.h 里面打开 以下这几个宏 SO_REUSE LWIP_TCP_KEEPALIVE 。<把這倆宏定義為1,默認為0,當然,其他的一些選項也在這個opt.h文件里>
2 在 tcp_impl.h 里面, 调整了以下三个宏的值:
1 A #define TCP_KEEPIDLE_DEFAULT 3000UL /7200000 ms ---> 3000ms/2 3 B #define TCP_KEEPINTVL_DEFAULT 1000UL /75000 ms ---> 1000ms/4 5 C #define TCP_KEEPCNT_DEFAULT 3U /9次 --> 3次/
當然,這三個宏其實是關乎keepalive的。就在這裡干吧。
代碼里大概就這麼干吧:
1 /*sock_reuse*/ 2 err = setsockopt(client_sock, SOL_SOCKET, 3 SO_REUSEADDR, 4 (const char*)&sock_reuse, sizeof(sock_reuse)); 5 if(err < 0) { 6 MY_DEBUG("setsockopt faild!\n\r"); 7 return FALSE_REUSEADDR; 8 } 9 10 11 int keepalive = 1; // 开启keepalive属性12 // int keepidle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测13 // int keepinterval = 5; // 探测时发包的时间间隔为5 秒14 // int keepcount = 3; // 探测尝试的次数。如果第1次探测包就收到响应了,则后2次的不再发。15 err = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive , sizeof(keepalive ));16 if(err < 0) {17 MY_DEBUG("%s, %d: Keep alive faild !\n\r",__func__,__LINE__);18 return FALSE_KEEPALIVE;19 }
OVER!
ps: 個人使用的版本為 rt_thread 1.2.2 + lwip 1.4.1 <也許其他版本有微調>
關鍵字: select lwip keepalive SO_REUSE