寫socket的“靈異事件”

最近寫了個java的服務器程序(基於Mina)在linux上運行,測試性能的時候發現任何一個request發送出去總是會有40ms的延遲再接受到response。一開始以爲是邏輯處理和算法的問題,優化了半天,發現延遲依然如故,即便最簡單的request也還需要40ms的延遲。而且,相同的一段程序,在我的windows筆記本上運行,就不存在這個40ms的延遲。

之所以文章題目取名“靈異”,是因爲我現在也只是找到了一個解決方案,但仍然不清楚問題出在什麼地方...

 

爲了解釋得更清楚,簡化了服務器的代碼,直接採用Mina example中的TimeServer。並且爲了排除網絡因素,將server和client部署在同一個機器上。

Server代碼:

 

這段代碼構造了一個簡單的Server,接收帶有"/r/n"後綴的request,然後獲取服務器的時間,將時間以字符串的形式發送回去。

 

客戶端發送程序如下:

這裏的變量id就是一個int型,需要將id轉換成string類型再進行發送。

所以,其實就是發送一個帶有“/r/n”後綴的字符串。

 

就是這樣一個簡單的request/response交互程序,每個消息的延遲都需要40ms。

於是將mina的logger打開,發現類似這樣的log:

 INFO 2011-01-07 11:32:08,402 [NioProcessor-2] (LoggingFilter.java:141) - RECEIVED: HeapBuffer[pos=0 lim=1 cap=1024: 33]
DEBUG 2011-01-07 11:32:08,402 [NioProcessor-2] (ProtocolCodecFilter.java:220) - Processing a MESSAGE_RECEIVED for session 1
 INFO 2011-01-07 11:32:08,447 [NioProcessor-2] (LoggingFilter.java:141) - RECEIVED: HeapBuffer[pos=0 lim=2 cap=1024: 0D 0A]
DEBUG 2011-01-07 11:32:08,447 [NioProcessor-2] (ProtocolCodecFilter.java:220) - Processing a MESSAGE_RECEIVED for session 1
DEBUG 2011-01-07 11:32:08,449 [NioProcessor-2] (TimeServerHandler.java:56) - message received
Message written...
 INFO 2011-01-07 11:32:08,449 [NioProcessor-2] (LoggingFilter.java:141) - SENT: HeapBuffer[pos=0 lim=0 cap=0: empty]
DEBUG 2011-01-07 11:32:08,450 [NioProcessor-2] (TimeServerHandler.java:74) - message sent

 

log的第一行表示,mina在402ms的時候接收到了一個byte的數據,這個byte的值是33,正好是id被轉換成String後的值。

log的第三行表示,mina在447ms的時候接收到了兩個byte的數據,分別是0D和0A,正好對應於"/r/n"。

而這兩次接收數據之間相差了447 - 402 = 45 ms。

幾乎等於每次交互的延遲。

 

所以,我至少發現了,40ms這麼多的時間都消耗在哪了。

但是,不明白的是,在客戶端,兩次output.write操作是按順序立即執行的,可服務器端顯示,它接收這兩個數據之間有40+ms的空隙。

 

於是再次修改了客戶端代碼,如下:

 

我用BufferedOutputStream發送數據,意味着,只有當output.flush()被調用的時候,write這個操作才真正的執行。

 

採用新的客戶端之後,服務器端的log如下:

 INFO 2011-01-07 12:01:29,438 [NioProcessor-2] (LoggingFilter.java:141) - RECEIVED: HeapBuffer[pos=0 lim=3 cap=1024: 33 0D 0A]
DEBUG 2011-01-07 12:01:29,439 [NioProcessor-2] (ProtocolCodecFilter.java:220) - Processing a MESSAGE_RECEIVED for session 1
DEBUG 2011-01-07 12:01:29,439 [NioProcessor-2] (TimeServerHandler.java:56) - message received
Message written...
 INFO 2011-01-07 12:01:29,439 [NioProcessor-2] (LoggingFilter.java:141) - SENT: HeapBuffer[pos=0 lim=0 cap=0: empty]
DEBUG 2011-01-07 12:01:29,441 [NioProcessor-2] (TimeServerHandler.java:74) - message sent

可以看出,40+ms的延遲已經沒有了。

並且,mina能夠一次就將客戶端發送的request完整的讀到。

另外,需要指出的是,即便在舊的客戶端程序的末尾添加"output.flush",40ms的延遲依然存在。

 

所以,目前爲止,我所知道的是,採用新的客戶端代碼可以解決40ms延遲這個問題。得到的教訓是,以後寫socket,儘量將一條消息一次性的寫入,而不要分幾次寫操作。

 

但是,40ms延遲的由來憑藉上面的實驗仍然看不清楚。特別是,之前提到過,舊的代碼如果在windows上運行一點問題沒有。這裏只提供兩個猜測:

猜測一:

客戶端的原因。衆所周知,當對socket調用write操作時,如果返回成功只是意味着將用戶內存中的數據寫入到了網卡的緩衝區中,並不意味着數據已經發送了。所以,雖然客戶端是連着調用了兩個write操作,但這隻意味着數據都被放入了網卡的緩衝區。而網卡在發送這兩個數據之間有可能產生一個40ms的間隙。

 

猜測二:

Mina底層採用的是Nio Selector這種IO多路複用的機制。一旦某個socket有數據就會產生一個read_event。那麼,一個可能的原因是客戶端的兩個write操作,數據到達服務器端時雖然是緊挨着,但mina產生了兩個read_event,又因爲某種原因,導致這兩個read_event之間出現了40ms的延遲...(這猜測我自己都有些聽不下去了)

 

總之,原因依舊不明。如果有高人願意指點一下就最好了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章