1. 程式人生 > >OkHttp的深入研究 一

OkHttp的深入研究 一

Okhttp是現在非常流行的網路框架,據說是可以取代我們自己手碼java.net.*或Org.apache的強大工具,那麼它到底有何優勢呢?在其官網上有如下描述:
OkHttp是一個高效的HTTP客戶端:
1 HTTP2支援允許所有有相同host的請求分享一個socket。
2 連線池降低了請求延時(如果http2無效)
3 GZIP壓縮了下載大小
4 快取Response,完全避免了網路重複請求。
當網路出問題的時候,OkHttp依然會屹立不倒:它會從常見的連線問題中悄悄恢復。如果你的服務有多個IP地址,它將會在第一個地址連線失敗之後嘗試連線其他幾個可選地址,這對於IPv4+IPv6的情況以及服務執行在多個備份資料中心的情況尤其重要。它支援TLS(與SSL同級類似)的新特性(SNI,ALPN),如果握手失敗它會退回到TLS1.0。 
OkHttp的使用非常簡單,它的request和response API使用了builder和非可變性來設計。它既支援同步阻塞也支援非同步回撥。
OkHttp支援android2.3及以上,java最低要求1.7。


要了解這些優點需要對http協議有一些認識:
1 相同host的請求分享一個socket是什麼意思?
在網路層有IP協議、ICMP協議、ARP協議、RARP協議和BOOTP協議。 
在傳輸層中有TCP協議與UDP協議。 
在應用層有:TCP包括FTP、HTTP、TELNET、SMTP等協議;UDP包括DNS、TFTP等協議。
這三層從底到高依次是網路層,傳輸層,應用層。那麼socket就是封裝了傳輸層的複雜操作提供給應用層的介面,無論應用層怎麼玩,玩的基本物件就是它,常聽說短連線長連線其實都是針對socket要不要保持這次請求不斷,而FTP、HTTP等都是建立在TCP協議之上的協議,相當於FTP協議規定一種socket的一套玩法、HTTP協議規定了另一種socket的一套玩法。長連線多用於操作頻繁,點對點的通訊,而且連線數不能太多情況。每個TCP連線都需要三步握手,這需要時間,如果每個操作都是先連線,再操作的話那麼處理速度會降低很多,所以每個操作完後都不斷開,次處理時直接傳送資料包就OK了,不用建立TCP連線。例如:資料庫的連線用長連線, 如果用短連線頻繁的通訊會造成socket錯誤,而且頻繁的socket 建立也是對資源的浪費。而像WEB網站的http服務一般都用短連結,因為長連線對於服務端來說會耗費一定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的連線用短連線會更省一些資源,如果用長連線,而且同時有成千上萬的使用者,如果每個使用者都佔用一個連線的話,那可想而知吧。所以併發量大,但每個使用者無需頻繁操作情況下需用短連好。
那麼回到問題上來,無論什麼協議,如果要發起一次請求,那麼需要建立socket再進行握手等等,握手成功才開始傳輸資料。對於HTTP協議來說,每一次發起請求,都需要重新走一遍這個初始化流程,比如說載入一個網頁,只要獲取資料展示出來就可以滿足使用者的需求了,這時我們就應當關閉這個通道,讓使用者自己玩去;但如果載入這個頁面需要發起許多個請求才能完成載入呢,比如需要獲取多個js和css檔案資料,這時就會出現有多少個請求就得走多少遍初始化流程。那麼相同host的請求分享一個socket,也就意味著這些初始化流程只需要走一次。知乎上有人提出如果這裡設定keep-alive使用長連線不是一樣的效果嗎?後來有高人貼了一張圖:

就是它的強大之處,它可以合併請求。而這個優點歸功於HTTP2協議,繼承了其前身SPDY協議的優良基因。

2 什麼是GZIP壓縮

實際上我們並不需要去了解什麼是GZIP壓縮,只需要知道它對傳輸的資料進行了壓縮,那麼傳輸效率肯定就增加了,但本著瞭解的心態查閱了一下,其壓縮方式還是比較獨特的。其原理是對一個檔案先進行ZL77演算法壓縮,再進行Huffman編碼壓縮。ZL77即是在一段資料裡依次找到多處相同的資料塊,保留第一塊的內容,用索引和大小來替換後面相同資料塊的內容。而Huffman編碼還沒有理解透。

3 其基本用法在其官網上也可以看到:

package okhttp3.guide;

import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class GetExample {
  OkHttpClient client = new OkHttpClient();

  String run(String url) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();

    Response response = client.newCall(request).execute();
    return response.body().string();
  }

  public static void main(String[] args) throws IOException {
    GetExample example = new GetExample();
    String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
    System.out.println(response);
  }
}

package okhttp3.guide;

import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class PostExample {
  public static final MediaType JSON
      = MediaType.parse("application/json; charset=utf-8");

  OkHttpClient client = new OkHttpClient();

  String post(String url, String json) throws IOException {
    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder()
        .url(url)
        .post(body)
        .build();
    Response response = client.newCall(request).execute();
    return response.body().string();
  }

  String bowlingJson(String player1, String player2) {
    return "{'winCondition':'HIGH_SCORE',"
        + "'name':'Bowling',"
        + "'round':4,"
        + "'lastSaved':1367702411696,"
        + "'dateStarted':1367702378785,"
        + "'players':["
        + "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39},"
        + "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}"
        + "]}";
  }

  public static void main(String[] args) throws IOException {
    PostExample example = new PostExample();
    String json = example.bowlingJson("Jesse", "Jake");
    String response = example.post("http://www.roundsapp.com/post", json);
    System.out.println(response);
  }
}