如何使用socket進行Http請求和解析響應


轉載自:https://www.jianshu.com/p/36f48b6ce223

前言

本篇文章是為講述okhttp源碼做一個鋪墊,主要是簡單講述一下socket的使用,因為在okhttp中網絡通訊使用的便是socket。但這篇文章不會涉及okhttp,會簡單闡述下socket,然后用代碼進行連接后http通訊,話不多說,開始干!

目錄
一、什么是Socket
二、如何使用Socket進行http請求
1、建立socket連接
2、http協議請求和響應格式解析
3、進行http請求

一、什么是Socket

回答這個問題前我們要先看下TCP/IP四層模型,想必這個圖大家都有見過,下面就解釋下這四層分別的表現形式是什么(理論解釋比較讓人摸不着頭腦,所以這里以其表現形式來闡述)

  1. 網絡接口層:主要表現為識別mac間比特流的傳輸
  2. 網絡層:表現為IP協議
  3. 傳輸層:表現為TCP、UDP
  4. 應用層:表現為Http、Https、RTSP等(這里的協議比較多,我們經常使用的http協議就屬於應用層)

Tip:順便說下TCP和UDP的區別。TCP提供可靠的通信傳輸,類似於打電話,需要等待另一方的接聽,才能進行真正的通訊;而UDP則不是可靠的,類似發短信,只將信息發出,至於對方有沒收到,這個就不關心了。

 
TCP/IP四層模型

而我們關心的socket是什么呢?socket其實是TCP連接的抽象,利用socket進行TCP的連接(這個解釋可能比較片面,但個人覺得是最為直觀的解釋,畢竟全面的解釋比較晦澀難懂)

二、如何使用Socket進行http請求

1、建立socket連接

在java中使用socket,其實非常的簡單。如果只是需要一個普通的socket,只需通過如下代碼,便可以建立一個socket連接

Socket socket = new Socket(“ip或域名”, 端口); 

如果想建立一個sslSocket,用於https的通訊(例如: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.1
Host: restapi.amap.com

(1)第一行為發起請求信息,其格式為:
  1. 發起的請求形式(這里使用的是GET,如果為POST的話,這里便為POST),即這里的“GET”
  2. 一個空格,即“ ”
  3. 請求的路徑(不包括域名,因為域名已經在建立socket連接時確定,如果為GET請求,則參數追加在后面以“?”隔開;如果為POST請求,則請求參數會在body中增加,具體見第四小點),即這里的“/v3/weather/weatherInfo?city=%E9%95%BF%E6%B2%99&key=13cb58f5884f9749287abbead9c658f2”
  4. 一個空格,即“ ”
  5. http請求的版本,即“HTTP/1.1”
  6. \r\n,此處沒有顯示出來,但是自己在組裝報文時,需要增加這個表示一行已經結束
(2)第二行的格式為:
  1. host字段名,即“Host”
  2. 一個冒號加一個空格,即“: ”(敲黑板!!冒號后面有一個空格,這個在組裝請求報文時,尤為重要)
  3. host的內容,即“restapi.amap.com”
  4. \r\n,此處沒有顯示出來,但是自己在組裝報文時,需要增加這個表示一行已經結束

tip:這里其實是請求頭部,如果頭部參數有多個的話,就按照這種格式進行拼裝。例如還有一個“Connection為keep-alive”的頭部參數,則以“Connection: keep-alive\r\n”的形式寫入輸出流中,具體會在后面的例子中展示。

(3)第三行的格式為:(沒想到吧!!!這里有第三行)
  1. \r\n,此處沒有顯示出來,但是自己在組裝報文時,需要增加這個表示頭部參數已寫完
(4)如果為POST請求,接下來還需要進行body參數的拼裝,這里以form表單為例,拼接上面接口的參數。規則就是“鍵=值”,鍵值對間用“&”隔開。
city=長沙&key=13cb58f5884f9749287abbead9c658f2

至此一個請求報文便拼裝完畢,將其用輸出流寫出即可獲得服務器的響應報文。

我們接着說下這次請求的響應報文

HTTP/1.1 200 OK Server: Tengine Date: Sun, 06 May 2018 08:22:10 GMT Content-Type: application/json;charset=UTF-8 Content-Length: 445 Connection: close X-Powered-By: ring/1.0.0 gsid: 010185222147152559493030300162313551811 sc: 0.013 Access-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)第一行為響應狀態,格式為
  1. http的版本信息,即“HTTP/1.1”
  2. 一個空格,即“ ”
  3. 狀態碼,即“200”
  4. 一個空格,即“ ”
  5. 狀態,即“OK”
  6. \r\n,此處沒有顯示出來,但是解析響應報文時,需要通過這兩個字符進行判斷是否一行結束
(2)第二行至第十二行為響應頭,每一行的格式為
  1. 頭名稱,即“Server”
  2. 一個冒號加一個空格,即“: ”
  3. 頭部參數值,即“Tengine”
  4. \r\n,此處沒有顯示出來,但是解析響應報文時,需要通過這兩個字符進行判斷是否一行結束
(3)第十三行,格式為
  1. \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都有很多,需要的時候進行搜查一下即可。記住我,我是猛猛的小盆友😄,如果我有理解錯誤或是寫的晦澀難懂的地方請與我聯系討論,共同進步。

 
 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM