1. 程式人生 > >如果服務端重啟,那麼客戶端的長連線會怎麼樣

如果服務端重啟,那麼客戶端的長連線會怎麼樣

這裡記錄一次服務端重啟時,使用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,就避免了在服務端沒有完全釋放前,我又連線了上去;

但其實這個問題,我還是沒法解釋原因。

)