目錄
一、前言
二、什麼是Socket
三、如何使用Socket進行http請求
1、建立socket連線
2、http協議請求和響應格式解析
3、進行http請求
四、寫在最後
一、前言
本篇文章是為講述okhttp原始碼做一個鋪墊,主要是簡單講述一下socket的使用,因為在okhttp中網路通訊使用的便是socket。但這篇文章不會涉及okhttp,會簡單闡述下socket,然後用程式碼進行連線後http通訊,話不多說,開始幹!
二、什麼是Socket
回答這個問題前我們要先看下TCP/IP四層模型,想必這個圖大家都有見過,下面就解釋下這四層分別的表現形式是什麼(理論解釋比較讓人摸不著頭腦,所以這裡以其表現形式來闡述)
- 網路介面層:主要表現為識別mac間位元流的傳輸
- 網路層:表現為IP協議
- 傳輸層:表現為TCP、UDP
- 應用層:表現為Http、Https、RTSP等(這裡的協議比較多,我們經常使用的http協議就屬於應用層)
Tip:順便說下TCP和UDP的區別。TCP提供可靠的通訊傳輸,類似於打電話,需要等待另一方的接聽,才能進行真正的通訊;而UDP則不是可靠的,類似發簡訊,只將資訊發出,至於對方有沒收到,這個就不關心了。
而我們關心的socket是什麼呢?socket其實是TCP連線的抽象,利用socket進行TCP的連線(這個解釋可能比較片面,但個人覺得是最為直觀的解釋,畢竟全面的解釋比較晦澀難懂)
二、如何使用Socket進行http請求
1、建立socket連線
在java中使用socket,其實非常的簡單。如果只是需要一個普通的socket,只需通過如下程式碼,便可以建立一個socket連線
Socket socket = new Socket(“ip或域名”, 埠);
複製程式碼
如果想建立一個sslSocket,用於https的通訊(例如:www.baidu.com)只需要通過sslSocketFactory進行建立sslSocket即可。程式碼如下:
Socket socket = SSLSocketFactory.getDefault().createSocket("www.baidu.com", 443);
複製程式碼
2、http協議請求和響應格式解析
在使用socket進行發起請求前,我們要先來了解下http協議。簡單一點的理解,http協議其實就是發起一個按照格式約定的字串,伺服器響應一串按格式組裝的資料。這裡不使用教科書式的資料格式,我們使用從”Restlet Client”發起一次請求,觀察其請求報文和響應報文來進行講解。
Tip:Restlet Client是一個api請求工具,日常開發中也可以用來向伺服器發起請求,獲取資料結構方便除錯。可以在chrome的應用商店下載。
這裡使用的api是高德的天氣預報介面,點選了“send”後,獲取到請求報文和響應報文,如下圖所示
我們先單獨說下這次請求的請求報文(第二個紅框中內容,如下所示)
GET /v3/weather/weatherInfo?city=%E9%95%BF%E6%B2%99&
key=13cb58f5884f9749287abbead9c658f2 HTTP/1.1Host: restapi.amap.com複製程式碼
(1)第一行為發起請求資訊,其格式為:
- 發起的請求形式(這裡使用的是GET,如果為POST的話,這裡便為POST),即這裡的“GET”
- 一個空格,即“ ”
- 請求的路徑(不包括域名,因為域名已經在建立socket連線時確定,如果為GET請求,則引數追加在後面以“?”隔開;如果為POST請求,則請求引數會在body中增加,具體見第四小點),即這裡的“/v3/weather/weatherInfo?city=%E9%95%BF%E6%B2%99&
key=13cb58f5884f9749287abbead9c658f2” - 一個空格,即“ ”
- http請求的版本,即“HTTP/1.1”
- \r\n,此處沒有顯示出來,但是自己在組裝報文時,需要增加這個表示一行已經結束
(2)第二行的格式為:
- host欄位名,即“Host”
- 一個冒號加一個空格,即“: ”(敲黑板!!冒號後面有一個空格,這個在組裝請求報文時,尤為重要)
- host的內容,即“restapi.amap.com”
- \r\n,此處沒有顯示出來,但是自己在組裝報文時,需要增加這個表示一行已經結束
tip:這裡其實是請求頭部,如果頭部引數有多個的話,就按照這種格式進行拼裝。例如還有一個“Connection為keep-alive”的頭部引數,則以“Connection: keep-alive\r\n”的形式寫入輸出流中,具體會在後面的例子中展示。
(3)第三行的格式為:(沒想到吧!!!這裡有第三行)
- \r\n,此處沒有顯示出來,但是自己在組裝報文時,需要增加這個表示頭部引數已寫完
(4)如果為POST請求,接下來還需要進行body引數的拼裝,這裡以form表單為例,拼接上面介面的引數。規則就是“鍵=值”,鍵值對間用“&
”隔開。
city=長沙&
key=13cb58f5884f9749287abbead9c658f2複製程式碼
至此一個請求報文便拼裝完畢,將其用輸出流寫出即可獲得伺服器的響應報文。
我們接著說下這次請求的響應報文
HTTP/1.1 200 OKServer: TengineDate: Sun, 06 May 2018 08:22:10 GMTContent-Type: application/json;
charset=UTF-8Content-Length: 445Connection: closeX-Powered-By: ring/1.0.0gsid: 010185222147152559493030300162313551811sc: 0.013Access-Control-Allow-Origin: *Access-Control-Allow-Methods: *Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,key,x-biz,x-info,platinfo,encr,enginever,gzipped,poiid{"status":"1","count":"2","info":"OK","infocode":"10000","lives":[{"province":"湖南","city":"長沙市","adcode":"430100","weather":"陣雨","temperature":"25","winddirection":"東北","windpower":"7","humidity":"78","reporttime":"2018-05-06 16:00:00"
},{"province":"湖南","city":"長沙縣","adcode":"430121","weather":"陣雨","temperature":"25","winddirection":"東北","windpower":"7","humidity":"78","reporttime":"2018-05-06 16:00:00"
}]
}複製程式碼
(1)第一行為響應狀態,格式為
- http的版本資訊,即“HTTP/1.1”
- 一個空格,即“ ”
- 狀態碼,即“200”
- 一個空格,即“ ”
- 狀態,即“OK”
- \r\n,此處沒有顯示出來,但是解析響應報文時,需要通過這兩個字元進行判斷是否一行結束
(2)第二行至第十二行為響應頭,每一行的格式為
- 頭名稱,即“Server”
- 一個冒號加一個空格,即“: ”
- 頭部引數值,即“Tengine”
- \r\n,此處沒有顯示出來,但是解析響應報文時,需要通過這兩個字元進行判斷是否一行結束
(3)第十三行,格式為
- \r\n,用於區分頭部引數和內容的區分
(4)第十四行為響應內容,格式為
這裡便是介面給我們的資料,即此處給到我們的天氣json資料,而此處json的長度為頭部中有一個引數為“Content-Length”決定的,例子中內容的長度為445。值得一提的是,有些介面返回的頭部引數並沒有“Content-Length”這一頭部引數,而是返回了“Transfer-Encoding: chunked”這樣的頭部引數,則表明是以塊的形式給到我們資料。塊的形式會以如下格式,第一行的“10\r\n”表明接下來的一行會有10個位元組的內容,第二行便是10位元組的內容,同樣以“\r\n”結束一行(\r\n這兩個字元不算在內容長度中),每一塊的格式都按這樣的形式,如果遇到“0\r\n\r\n”就說明內容結束。
10\r\n //(注意!!!這裡是10是16進位制,即如果進行內容讀取需要將其進行做10進位制的轉換)10位元組長度的內容\r\n//結束格式0\r\n\r\n複製程式碼
至此響應報文解析完畢。
3、進行http請求
逼逼叨逼逼叨了這麼久,很多小夥伴已經很迫不及待的想知道怎麼請求和獲取響應了。我們這裡便直接上程式碼,程式碼很簡單,並沒有什麼知識難點。
public class MySocket {
public static void main(String[] args) throws IOException {
//如果需要進行https的請求只需要換成如下一句(https的預設埠為443,http預設埠為80) //Socket socket = SSLSocketFactory.getDefault().createSocket("xxx", 443);
Socket socket = new Socket("restapi.amap.com", 80);
//獲取輸入流,即從伺服器獲取的資料 final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//獲取輸出流,即我們寫出給伺服器的資料 BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//使用一個執行緒來進行讀取伺服器的響應 new Thread() {
@Override public void run() {
while (true) {
String line = null;
try {
while ((line = bufferedReader.readLine()) != null) {
System.out.println("recv : " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
bufferedWriter.write("GET /v3/weather/weatherInfo?city=%E9%95%BF%E6%B2%99&
key=13cb58f5884f9749287abbead9c658f2 HTTP/1.1\r\n");
bufferedWriter.write("Host: restapi.amap.com\r\n\r\n");
bufferedWriter.flush();
}
}複製程式碼
跑起來後會看到控制檯輸出如下資訊,這個時候我們就可以按照第二小結中的格式進行解析到一個模型中,最終返回給UI或是邏輯層去使用。
四、寫在最後
OkHttp中使用socket連線後,進行處理響應便是這樣的處理邏輯。只是它還有對socket的複用,連線進行限制之類的優化處理,這個在後面的文章中會進行剖析。如果您期待這樣的剖析之旅的話,給個“❤️”加個關注吧!文章中並沒有對頭部引數進行說明其含義,這裡也不打算給出,其實百度一下或google都有很多,需要的時候進行搜查一下即可。記住我,我是猛猛的小盆友?,如果我有理解錯誤或是寫的晦澀難懂的地方請與我聯絡討論,共同進步。