l

2011年4月13日 星期三

時間到

April 13 21:08~22:19

相信鄉民們一定有那種和別人約好要見面,可是卻苦等不到對方的經驗。如果是男女朋友約會,通常是男方比較早到,女方遲到個 30 分鐘算是正常,遲到 1 - 2 個小時也不算太超過。在熱戀中的男方,心目中的 timeout 可能是 > N 小時,但是一旦 騙到手 結婚之後,timeout 時間可能會被調整成 < 5 分鐘,真是太現實了。

不管如何,具有 timeout 的觀念是很重要的,真實世界沒有什麼『不見不散』,『等你一生一世』的那種事,像『王寶釧』這種把 timeout 設成『18 年』以上的人,已經算是瀕臨絕種的稀有寶物了。

***

Timeout 對於 programmers 而言也是很重要的觀念,之前 Teddy 在『還少一本書:Release It! Design and Deploy Production-Ready Software 』有稍微介紹一下書中 Use Timeouts 這個 stability pattern。今天 Teddy 解了一個 bug,剛好也是和 timeout 有關,順便再幫鄉民們複習一下。

這個 bug 和使用 Java 建立 socket 有關,假設你在寫網路應用程式,有下列幾個步驟:
  1. client 建立一個 socket 連到 server。
  2. client 透過 socket 得到一個 output stream,利用這個 output stream 送出 request。
  3. client 透過 socket 得到一個 input stream,利用這個 input stream 讀取 server 傳回來的 response。
以上為一個很典型的網路應用,由於 Teddy 有看過 Release It 這本書,知道開發網路應用程式要套用 Timeouts 這個 pattern。經過分析,上述第 3 個步驟可能會因為 server 很忙以至於過了很久都沒有把結果傳回來,導致 client 『卡住』,很容易讓使用者以為程式當掉,所以這邊就要利用到 timeout 機制。還好 Java 都有幫鄉民們考慮到這個問題,Socket 物件有一個 setSoTimeout method ,看一下 JavaDoc 的說明:

setSoTimeout

public void setSoTimeout(int timeout)
                  throws SocketException
Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.
Parameters:
timeout - the specified timeout, in milliseconds.
以上 JavaDoc 說明已經很清楚了,但是,只有 read 會卡住嗎?Teddy 今天遇到的問題就是卡在個步驟 1。Teddy 用一個簡單一點的例子來說明,假設你的程式 ping 某個 IP address,在正常的情況下,很快就會有回應,如下圖所示。




如果 ping 一個沒人使用的 IP address,應該會出現如下的結果,也是很快就會有回應


但是,如果隨便 ping 一個 IP address,整個程式就會卡住,如下圖所示。


如果你的程式是呼叫 public Socket (InetAddress address, int port) 來建立 socket,那麼就可能在個步驟 1 卡住。看一下這個 constractor 的 JavaDoc:

public Socket(InetAddress address,
              int port) throws IOException

Creates a stream socket and connects it to the specified port number at the specified IP address. If the application has specified a socket factory, that factory's createSocketImpl method is called to create the actual socket implementation. Otherwise a "plain" socket is created.

使用這個 constractor 當 Socket 物件被建立的時候,同時也 connect 到遠端。仔細看一下 Socket 物件所提供的 9 個 constractor,沒有一個可以指定 timeout 的,所以程式要改成下面這樣。

  1. 使用這個 Socket() 不帶參數的 constractor,根據 JavaDoc 的說明這個 constractor『Creates an unconnected socket』。
  2. 使用 connect (SocketAddress endpoint, int timeout) 來建立連線,根據 JavaDoc 的說明『Connects this socket to the server with a specified timeout value. A timeout of zero is interpreted as an infinite timeout. The connection will then block until established or an error occurs. 』。
故事就這麼簡單,程式改完之後建立連線時就不會一直卡住了。由於 connect (SocketAddress endpoint, int timeout) 是在 JDK 1.4 版之後才出現的,所以鄉民們到網路上亂找範例的時候,可能找到類似呼叫 Socket(InetAddress address, int port) 這樣的範例(產生 Socket 物件並建立連線)就給它直接抄過來用,所以這類的程式碼(建立 connection 時沒有指定 timeout)其實還滿常見的。

***

如果講到這裡就結束那就遜掉了,還有一個重點是這種 bug 其實不太好找,為什麼?因為同樣的 code 在 Windows 上面並不會有問題,而在 Linux 上面卻會。不要傻傻以為 Java 是跨平台,Java 底層很多程式還是呼叫 native code,網路程式就是一個例子,所以有一些行為還是跟作業系統相關的。總之,跟著 Teddy 大聲念三遍:

寫網路應用程式的時候要記得 Use Timeouts 。


***

友藏內心獨白:如果要害一個人,就送他一張不限航點的飛機艙位升等券...這一句有緣人才看得懂...XD

5 則留言:

  1. 其實之前在開發WiMAX專案時還遇到更好玩的例子,
    由於是用TCP去模擬虛擬的無線網路通道,
    ping封包也是用TCP送,
    因此會看到2xxx~3xxx ms不等的值出現。

    回覆刪除
  2. 題外話,
    Java網路程式我都是用無參數Socket()建立物件後再呼叫connect()...

    回覆刪除
  3. Hi sprint,

    那你平常在呼叫 connect() 的時候會採用有 timeout 的那個版本嗎?

    回覆刪除
  4. Google '飛機艙位升等券', 會有有趣的現象

    回覆刪除