使用Socket進行HTTP請求與報文講解

目錄
1、前言
2、什麼是Socket
3、如何使用Socket進行http請求
一、創建socket鏈接
二、http協議請求和響應格式解析
三、進行http請求
4、寫在最後
java

1、前言

本篇文章是爲講述okhttp源碼作一個鋪墊,主要是簡單講述一下socket的使用,由於在okhttp中網絡通信使用的即是socket。但這篇文章不會涉及okhttp,會簡單闡述下socket,而後用代碼進行鏈接後http通信,話很少說,開始幹!chrome

2、什麼是Socket

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

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

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

TCP/IP四層模型

而咱們關心的socket是什麼呢?socket實際上是TCP鏈接的抽象,利用socket進行TCP的鏈接(這個解釋可能比較片面,但我的以爲是最爲直觀的解釋,畢竟全面的解釋比較晦澀難懂)bash

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

一、創建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);
複製代碼

二、http協議請求和響應格式解析

在使用socket進行發起請求前,咱們要先來了解下http協議。簡單一點的理解,http協議其實就是發起一個按照格式約定的字符串,服務器響應一串按格式組裝的數據。 這裏不使用教科書式的數據格式,咱們使用從"Restlet Client"發起一次請求,觀察其請求報文和響應報文來進行講解。數據結構

Tip:Restlet Client是一個api請求工具,平常開發中也能夠用來向服務器發起請求,獲取數據結構方便調試。能夠在chrome的應用商店下載。app

這裏使用的api是高德的天氣預報接口,點擊了「send」後,獲取到請求報文和響應報文,以下圖所示socket

咱們先單獨說下此次請求的請求報文(第二個紅框中內容,以下所示)

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
複製代碼

至此響應報文解析完畢。

三、進行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或是邏輯層去使用。

運行結果

4、寫在最後

OkHttp中使用socket鏈接後,進行處理響應即是這樣的處理邏輯。只是它還有對socket的複用,鏈接進行限制之類的優化處理,這個在後面的文章中會進行剖析。若是您期待這樣的剖析之旅的話,給個「❤️」加個關注吧!文章中並無對頭部參數進行說明其含義,這裏也不打算給出,其實百度一下或google都有不少,須要的時候進行搜查一下便可。記住我,我是猛猛的小盆友😄,若是我有理解錯誤或是寫的晦澀難懂的地方請與我聯繫討論,共同進步。

相關文章
相關標籤/搜索