HTTP請求設置setReadTimeout防止請求超時真的有效嗎?

<strong>如果我們要用HTTP去請求一個很大的資源,或者預先判斷由於網絡因素,請求時間可能會很長,就要考慮超時了。給請求設置個ConnectTimeout或者ReadTimeout,一般來說就能解決大部分問題了。但是,這就夠了嗎?

今天有位童鞋,發了個鏈接http://93.175.xx.xx:8008/,說爬這個網址的時候,IO會一直卡在那,一直沒有返回響應。 那個網址是他發現的一個特殊請求,輸出一個視頻流,但是服務器端不返回Content-Length,也不輸出真實數據,就是輸出不到1024字節的流後就一直停在那也不close,瀏覽器打開的效果就是看到了視頻的前幾幀,然後一直卡在哪轉圈。

這麼說來,感覺不是個大問題,設置下ReadTimeout不就好了麼,童鞋說他也設置了,但是無效,他使用的python代碼實現,剛開始覺得是他代碼的問題,或者那個API庫實現的問題,偏不信,咱就用Java也實現了一把

<code>package sms.bai.util;import com.squareup.okhttp.Headers;import com.squareup.okhttp.OkHttpClient;import com.squareup.okhttp.Request;import com.squareup.okhttp.Response;import java.io.IOException;import java.util.concurrent.*;public class Req {    public static void reqUrl() throws IOException {        OkHttpClient client = new OkHttpClient();        client.setConnectTimeout(5,TimeUnit.SECONDS);        client.setReadTimeout(5,TimeUnit.SECONDS);        Request request = new Request.Builder()                .url("http://93.175.29.89:8008/")                .build();        Response response = client.newCall(request).execute();        if (!response.isSuccessful()) {            throw new IOException("服務器端錯誤: " + response);        }        Headers responseHeaders = response.headers();        for (int i = 0; i < responseHeaders.size(); i++) {            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));        }        System.out.println(response.body().string());    }    public static void main(String[] args) throws IOException {        reqUrl();    }}/<code>

果然如其所言,無論設置ConnectTimeout還是ReadTimeout都是無效的,代碼一直停留在輸出那裡,不輸出任何body(瀏覽器裡還能勉強看到畫面),程序也不stop

<code>Content-Type: multipart/x-mixed-replace;boundary=---nessy2jpegboundaryOkHttp-Sent-Millis: 1582028133591OkHttp-Received-Millis: 1582028133875/<code>

這裡用的是OkHttp庫 ,換其它庫或者用Java自帶的HttpUrlConnection理論上效果也是一樣的。

使用ffmpeg識別一下:

<code>ffmpeg -i http://93.175.29.xx:8008/ -f mp4 out.mp4/<code>

ffmpeg 能識別出是一個視頻流,但是會一直卡在frame=xx這裡,一直讀取幀而不停止。強行終止後能輸出一個時長有幾秒的視頻

看來依靠HttpUrlConnection中的SocketTimeoutException是無解了,只能在外面套一層了。main方法改成如下

<code>public static void main(String[] args) throws Exception {        final ExecutorService exec = Executors.newFixedThreadPool(1);        Callable<string> call = new Callable<string>() {            public String call() throws Exception {                //開始執行耗時操作               reqUrl();                return "線程執行完成.";            }        };        Future<string> future = null;        try {            future = exec.submit(call);            String obj = future.get(1000 * 10, TimeUnit.MILLISECONDS); //任務處理超時時間設為 10 秒            System.out.println("任務成功返回:" + obj);        } catch (TimeoutException ex) {            System.out.println("處理超時啦....");            ex.printStackTrace();            future.cancel(true);        } catch (Exception e) {            System.out.println("處理失敗.");            e.printStackTrace();        }finally {            // 關閉線程池            System.out.println("關閉線程池");            exec.shutdown();        }    }/<string>/<string>/<string>/<code>

這下能得到期望的結果了

<code>Content-Type: multipart/x-mixed-replace;boundary=---nessy2jpegboundaryOkHttp-Sent-Millis: 1582028854911OkHttp-Received-Millis: 1582028855178處理超時啦....java.util.concurrent.TimeoutExceptionat java.util.concurrent.FutureTask.get(FutureTask.java:205)at sms.bai.util.Req.main(Req.java:47)關閉線程池Process finished with exit code 0/<code>

那這個HttpUrlConnection裡的超時到底是啥意思呢?為什麼無效呢?看一下文檔。 ConnectTimeout , Java 是這樣解釋的:

Sets a specified timeout value, in milliseconds, to be used when opening a communications link to the resource referenced by this URLConnection. If the timeout expires before the connection can be established, a java.net.SocketTimeoutException is raised. A timeout of zero is interpreted as an infinite timeout.

Some non-standard implmentation of this method may ignore the specified timeout. To see the connect timeout set, please call getConnectTimeout().

意思是用來建立連接的時間。如果到了指定的時間,還沒建立連接,則報異常。 這個比較好理解。

ReadTimeout , Java 是這樣解釋的:

Sets the read timeout to a specified timeout, in milliseconds. A non-zero value specifies the timeout when reading from Input stream when a connection is established to a resource. If the timeout expires before there is data available for read, a java.net.SocketTimeoutException is raised. A timeout of zero is interpreted as an infinite timeout.

Some non-standard implementation of this method ignores the specified timeout. To see the read timeout set, please call getReadTimeout().

意思是已經建立連接,並開始讀取服務端資源。如果到了指定的時間,沒有可能的數據被客戶端讀取,則報異常。

也就是說setReadTimeout not mean read complete, it mean when wait for 10s, when there’re no more data read in, will throw a timeoutexception。

所以針對這種特殊的服務器構造的異常流,是沒法用SocketTimeoutException來解決超時的,只能在外面再設置一層,通過線程的超時來控制。

另外提一句,python是通過設置gevent超時來解決的,原理是一樣的。

tips:看了此文,你真正瞭解超時設置了嗎?


分享到:


相關文章: