Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android網絡編程(二)--Http協議詳解

Android網絡編程(二)--Http協議詳解

編輯:關於Android編程

在上一篇中我們討論了計算機網絡的體系結構和各層次的作用,在我們編程中TCP或UDP都提供了socket接口進行實現,實現的例子在上一篇中,這一篇我們主要討論一下Http協議,以及如何實現Http協議。

討論的問題:

Http協議的定義和內容。 Http協議的實現。

一、Http協議的定義和內容

HTTP的英文是(HyperText Transfer Protocol),即超文本傳輸協議,那麼什麼是超文本呢,超文本就是使用超文本標記語言HTML的文本。

首先互聯網上每一個資源都用一個URL所標記,URL的格式為:

http://<主機>:<端口>/<路徑>

它是應用層協議,規定了數據交互的格式內容。在運輸層,它采用了TCP協議保證了可靠運輸。而且HTTP協議是無狀態的,不過在HTTP/1.1中,添加了持續連接的功能。
下面就來看看HTTP報文的格式:
首先它有兩種報文:

 - 請求報文(客戶端發送給服務器的報文)
 - 響應報文(服務器返回給客戶端的報文)

而且HTTP是面向文本的,所以在報文中的每一個字段都是一些ASCII碼。

請求報文:

請求報文包括 請求行、請求頭部、請求數據
這裡寫圖片描述
這裡面第一行是請求行,舉一個例子:

GET http://s2-im-notify.csdn.net/socket.io/1/xhr-polling/4-edxSXeVplQjhPcXY2V?t=1476099463098 HTTP/1.1
Host: s2-im-notify.csdn.net
Connection: keep-alive
Origin: http://blog.csdn.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
Accept: */*
Referer: http://blog.csdn.net/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8

請求行:
在這個請求中我們看到這個請求行為第一行,下面的都是請求頭了,這個並沒有請求數據。這個請求中請求方法為GET,GET是我們常用的請求方法之一,除了GET,我們還常用POST進行請求。
下面列舉出請求方法,不過最常用的還是GET和POST:

OPTION:請求一些選項的信息 GET:請求讀取由URL所標志的信息 HEAD:請求讀取由URL所標志的信息的首部 POST:給服務器發送信息 PUT:在指明的URL下存儲一個文檔 DELETE:刪除指明的URL所標志的資源 TRACE:用來進行環回測試的請求報文

CONNECT:用於代理服務器
請求報頭:
請求報頭有許多種,下面說一些常見的請求頭:

Host域:指定請求的服務器地址,在HTTP/1.1中請求必須包含主機頭域,否則系統會以400狀態碼返回。

Connection域:指定與連接相關的屬性,比如上面例子中Connection: keep-alive,代表保持連接。 User-Agent域:發送請求的應用程序名稱。 Accept-Charset域:通知服務端可以發送的編碼格式。 Accept-Encoding域:通知服務端可以發送的數據壓縮格式。 Accept-Language域:通知服務器可以發送的語言。 Accept域: 告訴WEB服務器自己接受什麼介質類型,/ 表示任何類型,type/* 表示該類型下的所有子類型。 Referer域:發送請求頁面URL。浏覽器向 WEB 服務器表明自己是從哪個 網頁/URL 獲得/點擊 當前請求中的網址/URL。 Pramga域:主要使用 Pramga: no-cache,相當於 Cache-Control: no-cache。 Date域:表示消息發出的時間。 Cookie域:設置Cookie相關的。 Cache-Control域:使用的緩存機制(在請求時和響應時它的值不同,響應的下文會說到)。
no-cache(不要緩存的實體,要求現在從服務器去取)
max-age:(只接受 Age 值小於 max-age 值,並且沒有過期的對象)
max-stale:(可以接受過去的對象,但是過期時間必須小於 max-stale 值)
min-fresh:(接受其新鮮生命期大於其當前 Age 跟 min-fresh 值之和的緩存對象)。

響應報文

類比請求報文,響應報文同樣也包括 響應行、響應頭、響應數據
這裡寫圖片描述
下面也是用一個例子來看一下:

HTTP/1.1 200 OK
Server: openresty
Date: Mon, 10 Oct 2016 12:25:25 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
Vary: Accept-Encoding
Cache-Control: private
Access-Control-Allow-Origin: *
Content-Encoding: gzip

6b0
        X[  F~G ?  $ I  @  eiWb
j }Bc{ k ]{r F+A/Z} T   U/-  [ cJ   _ _ 
     6 g s wΜs ? #g /  sa     
O [^

*** FIDDLER: RawDisplay truncated at 128 characters. Right-click to disable truncation. ***

響應行:
第一行為響應行,對於響應行,我們可以看出狀態碼(也成為響應碼)為200。
關於狀態碼,都是由三位數字組成的,分為五大類:

1XX :表示通知信息,如請求收到了或者正在進行處理。 2XX:表示成功,其中最常見的便是200,表示請求成功。 3XX:表示重定向,如要完成請求還必須采取進一步的行動。 4XX:表示客戶端出錯,最常見的就是404,表示未找到。 5XX:表示服務器出錯,比如服務器失效無法完成請求。
除了上面的還有一些常見的響應頭,比如:
HTTP/1.1 202 Accepted //表示接受
HTTP/1.1 400 Bad Request //表示錯誤的請求
HTTP/1.1 404 Not Found //表示未找到
HTTP/1.1 301 Moved Permanently //永久性的轉移了
響應頭:
列舉一下常用的頭部,(PS:在百度時發現一篇寫關於這個頭部和狀態碼非常詳細的文章,我有的頭部也參考的他的博客,分享給大家:/net/201607/528141.html) Age域:當代理服務器用自己緩存的實體去響應請求時,用該頭部表明該實體從產生到現在經過多長時間了。 Accept-Ranges域:WEB服務器表明自己是否接受獲取其某個實體的一部分(比如文件的一部分)的請求。bytes:表示接受,none:表示不接受。 Content-Type域:服務器通知客戶端它響應的對象類型,如Content-Type:application/json。

Content-Range域:服務器表明該響應包含的部分對象為整個對象的哪個部分。

Content-Length:服務器通知響應包含對象的長度。

Content-Language:服務器通知客戶端響應的語言。 Content-Encoding:服務器通知客戶端響應數據的壓縮格式,在上面的例子中可以看出壓縮格式為gzip。 Connection:代表是否需要持久連接。 Expired:WEB服務器表明該實體將在什麼時候過期,對於過期了的對象,只有在跟WEB服務器驗證了其有效性後,才能用來響應客戶請求。 Last-Modified: 服務器認為對象的最後修改時間,比如文件的最後修改時間,動態頁面的最後產生時間等等。 Location:服務器告訴浏覽器,試圖訪問的對象已經被移到別的位置了,到該頭部指定的位置去取。 Proxy-Authenticate:代理服務器響應浏覽器,要求其提供代理身份驗證信息。 Server: 服務器表明自己是什麼軟件及版本等信息。 Refresh:表示浏覽器應該在多少時間之後刷新文檔,以秒計。
響應數據:
一般來說響應數據也會有一定的格式,上面的例子中因為數據用gzip壓縮了,所以顯示的為亂碼。現在用的最火的為json格式數據,下面的例子為一個請求的響應數據,為json格式:
{“status”:true,”error”:”“,”data”:{“id”:010101,”url”:”http://blog.csdn.net/liushuaiq/article/details/52779689“}}

二、Http協議的實現

上一篇中我們使用了java提供的socket進行了數據的傳輸,socket是對tcp或udp的封裝,對於應用層沒有實現,這一篇就對上一篇進行進一步的拓展,實現Http協議。
我們只需要在socket傳輸數據的基礎上,進行進一步的格式化數據就可以了。
首先我封裝了一個請求實體。

package com.liushuai.model;

import java.util.List;

public class Request {
    private RequestLine requestLine;
    private List requestHeaders;
    private RequestBody requestBody;

    public Request() {
        super();
    }

    public Request(RequestLine requestLine, List requestHeaders, RequestBody requestBody) {
        super();
        this.requestLine = requestLine;
        this.requestHeaders = requestHeaders;
        this.requestBody = requestBody;
    }

    public RequestLine getRequestLine() {
        return requestLine;
    }

    public void setRequestLine(RequestLine requestLine) {
        this.requestLine = requestLine;
    }

    public List getRequestHeaders() {
        return requestHeaders;
    }

    public void setRequestHeaders(List requestHeaders) {
        this.requestHeaders = requestHeaders;
    }

    public RequestBody getRequestBody() {
        return requestBody;
    }

    public void setRequestBody(RequestBody requestBody) {
        this.requestBody = requestBody;
    }

}

其中裡面還進行進一步封裝了請求行和請求頭和請求體,代碼如下:

package com.liushuai.model;

/**
 * 請求行實體
 * 
 * @author LiuShuai
 *
 */
public class RequestLine {
    /**
     * 請求方法
     */
    private String method;
    /**
     * 請求的 URL
     */
    private String url;
    /**
     * 版本
     */
    private String version;

    public RequestLine() {
        super();
    }

    public RequestLine(String method, String url, String version) {
        super();
        this.method = method;
        this.url = url;
        this.version = version;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

}
package com.liushuai.model;

/**
 * 請求頭部實體
 * 
 * @author LiuShuai
 *
 */
public class RequestHeader {
    /**
     * 頭部名稱
     */
    private String name;
    /**
     * 頭部域值
     */
    private String value;

    public RequestHeader() {
        super();
    }

    public RequestHeader(String name, String value) {
        super();
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}
package com.liushuai.model;

/**
 * 請求體
 * 
 * @author LiuShuai
 *
 */
public class RequestBody {

    private String requestBody;

    public RequestBody() {
        super();
    }

    public RequestBody(String requestBody) {
        super();
        this.requestBody = requestBody;
    }

    public String getRequestBody() {
        return requestBody;
    }

    public void setRequestBody(String requestBody) {
        this.requestBody = requestBody;
    }
    }

下面是在socket的基礎上將數據進行封裝,然後格式化為Http協議中要求的格式輸出:

package com.liushuai.client;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import javax.management.relation.Relation;
import javax.sql.rowset.serial.SerialArray;

import com.liushuai.model.Request;
import com.liushuai.model.RequestHeader;
import com.liushuai.model.RequestLine;
import com.liushuai.server.MyServer;

public class MyClient {
    public static void main(String[] args) {
        InetAddress inet4Address;
        try {
            inet4Address = Inet4Address.getLocalHost();
            Socket socket = new Socket("123.126.51.32", 80);
            //請求行
            RequestLine rLine = new RequestLine("GET",
                    "http://download.pinyin.sogou.com/picface/interface/cellupdate.php?h=53B24101A353034C6F75D2DB4436AA80&v=8.0.0.8381&r=0000_sogou_pinyin_win10a&ver=1.0.0.1464&cellid=80|77|79|78|2|37|41|47|52|54&cellver=2|3|2|2|4|2|4|5|3|6",
                    "HTTP/1.1");
            //請求頭
            RequestHeader rh1 = new RequestHeader("User-Agent", "SogouComponentAgent");
            RequestHeader rh2 = new RequestHeader("Host", "download.pinyin.sogou.com");
            RequestHeader rh3 = new RequestHeader("Pragma", "no-cache");
            RequestHeader rh4 = new RequestHeader("Cookie",
                    "YYID=53B24101A353034C6F75D2DB4436AA80; IMEVER=8.0.0.8381; IPLOC=CN3702");
            List rhs = new ArrayList<>();
            rhs.add(rh1);
            rhs.add(rh2);
            rhs.add(rh3);
            rhs.add(rh4);
            //構造請求,這裡面讓請求體為空了
            Request request = new Request(rLine, rhs, null);

            // 拼裝一個Http請求
            StringBuffer requestString = new StringBuffer();
            // 拼裝請求行
            RequestLine reqLine = request.getRequestLine();
            StringBuffer line = new StringBuffer();
            line.append(reqLine.getMethod()).append(" ").append(reqLine.getUrl()).append(" ")
                    .append(reqLine.getVersion()).append("\r\n");
            requestString.append(line);
            // 拼裝請求頭部
            List requestHeaders = request.getRequestHeaders();
            StringBuffer headers = new StringBuffer();
            for (RequestHeader h : requestHeaders) {
                headers.append(h.getName()).append(":").append(h.getValue()).append("\r\n");
            }
            requestString.append(headers).append("\r\n");
            if (request.getRequestBody()!= null) {
                requestString.append(request.getRequestBody().getRequestBody());
            }
            //向服務器發送請求的數據,數據已經拼裝成了Http請求報文的格式
            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
            printWriter.print(requestString.toString());
            printWriter.flush();

            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            StringBuffer responseString = new StringBuffer(); 
            String servervInfo ;
            //讀取服務器的響應,不過這地方會阻塞,因為Java I/O是線程阻塞的,優化一下代碼應當使用Java NIO
            while((servervInfo = reader.readLine())!="\r\n"&&servervInfo!=null){
                System.out.println(servervInfo);
                responseString.append(servervInfo+"\n");
            }
            System.out.println("服務器端發送的數據為--->" + responseString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

這就簡單的模仿了一下Http的一次請求,我將返回結果進行了打印:

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 11 Oct 2016 12:28:29 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-Powered-By: PHP/7.0.8
Pragma: cache
Cache-Control: public, must-revalidate, max-age=0
Accept-Ranges: bytes
Content-Disposition: filename="SGPicFaceCellList.ini"

可以看出這次是請求成功了,返回的格式也符合Http中要求的格式。
這一篇就對HTTP協議進行了一次詳細的介紹,下一篇中我准備分析一下當前最火的Android網絡框架OkHttp源碼中的實現。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved