如果服務端重啟,那麼客戶端的長連線會怎麼樣
這裡記錄一次服務端重啟時,使用winshark的抓包過程;
場景是:SDK 建立對 服務端的長連線,客戶端連線策略是:
失活判斷: 一條連線 180s都沒有read到資料;
保活判斷: 每秒檢查一次,連續60次檢查都為空閒,那麼傳送一次keeplive包。
重連邏輯: 如果連線斷開,那麼會以2s 、 4s、 6s、 8s...這樣的遞增產生的時延,去重連,每次連線等待5s判斷連線超時而被認為連線失敗。
客戶端首先建立好對服務端的連線,然後關閉服務端,比如kill指令;
抓包分析:
1.由於是服務端被kill了,那麼client立即感知到連線被關閉,recv=0;
2.此時服務端正在回收各種資源中,包括socket的資源,而客戶端2s後執行斷開重連的操作,居然連線成功了,併發送訊息成功了。客戶端這條連線被認為是成功。
3.問題來了,大約過了一會,服務端會發送一個rst指令給客戶端,這個是通過抓包看得到的,然而使用ndk編碼實現的sdk,工作在Android模擬器裡,居然沒有觸發select的
讀事件;因為沒有被立即觸發,那麼客戶端就要等到一個超時週期,直到判斷連線失活了,才closesocket。而在此期間,這條tcp連線都被認為是成功的。
問題是:為什麼既然對端發了一個rst指令,而此端的select怎麼沒有檢查到readable事件呢;程式碼如下:
void CTCPSocket::OnSelectEvent() { struct timeval tv ; tv.tv_sec = 0; tv.tv_usec = 1000*100 ; fd_set readset ; fd_set sendset ; fd_set exceptionalset; FD_ZERO( &readset ) ; FD_ZERO( &exceptionalset ); FD_ZERO( &sendset ) ; FD_SET( m_hSocket, &readset ) ; FD_SET( m_hSocket, &exceptionalset ) ; fd_set * pSendSet = NULL ; if ( CheckWriteEvent() ) { FD_SET( m_hSocket, &sendset ) ; pSendSet = &sendset ; } DEBUG_LOG("SELECT"); int rc = select( m_hSocket+1, &readset, pSendSet , &exceptionalset, &tv ) ; if (rc < 0) { m_pNetHandler->OnError( "CTCPSocket::Execute select err...%d", errno ) ; ErrEvent() ; return; } if (1==m_InConnected) { DEBUG_LOG("OnSelectEvent checkConnectEvent"); ConnectEvent( &readset, &sendset ) ; return; } int nErr = 0 ; if ( FD_ISSET(m_hSocket,&exceptionalset) ) { DEBUG_LOG("exceptionalEvent()"); } if ( FD_ISSET( m_hSocket, &readset ) ) { DEBUG_LOG("ReadEvent()"); nErr = ReadEvent() ; } if (/* nErr == 0 && pSendSet != NULL && */FD_ISSET( m_hSocket, &sendset) ) { DEBUG_LOG("SendEvent()"); nErr = SendEvent() ; } if ( nErr != 0 ) { ErrEvent() ; } }
難道是我的這段基於select io 模式的程式碼有問題?
然後我首先使用python寫了一段select的程式碼,做相同的測試,發現很快就觸發了readevent;
然後我再把這段程式碼在linux執行,也很快被對端的rst指令觸發了readevent事件;
相同程式碼,在不同的平臺的執行效果,可能就是不如預期的。除非把這一段程式碼,在不同的平臺都做了完整的測試。這段程式碼被使用在客戶端裡,由此可以體會到客戶端開發的一個難點,就是跨平臺性。
我想不同平臺對select的實現,是不是並不是完全一致的。比如我使用的Android模擬器就是一個linux核心的裁剪版本。這隻能算一個猜測,但是這足以說明寫一個平臺的程式碼,是不能完全用在另一個平臺的經驗做主觀上完全沒問題的斷定的。實際,相同程式碼的執行在不同的平臺上執行結果,依舊充滿不確定性吧。
那麼針對我這個問題,保險起見,可以每次select之後,都可以直接觸發一次readevent;以保證客戶端程式碼的可用性吧。
(PS: 這樣也解決不了問題:涉及測試時 read操作總是忙;最後我是怎麼解決的呢,把斷開連線重連的時延設定得更大了一旦,比如我我這次設定為6s,就避免了在服務端沒有完全釋放前,我又連線了上去;
但其實這個問題,我還是沒法解釋原因。
)