HTTP 必知必會的那些

2021-02-14 玉剛說

本文由玉剛說寫作平臺提供寫作贊助
贊助金額:200元
原作者:竹千代
版權聲明:本文版權歸微信公眾號玉剛說所有,未經許可,不得以任何形式轉載

Http是我們經常打交道的網絡應用層協議,它的重要性可能不需要再強調。但是實際上很多人,包括我自己可能對http了解的並不夠深。本文就我自己的學習心得,分享一下我認為需要知道的緩存所涉及到的相關知識點。

Http報文

首先我們來點基礎的,看看http報文具體的格式。http報文可以分為請求報文和響應報文,格式大同小異。主要分為三個部分:

起始行

首部

主體

請求報文格式:

<method> <request-url> <version>
<headers>

<entity-body>

響應報文格式

<version> <status> <reason-phrase>
<headers>

<entity-body>

從請求報文格式和響應報文格式可以看出,兩者主要在起始行上有差異。這裡稍微解釋一下各個標籤:

<method> 指請求方法,常用的主要是Get、 Post、Head 還有其他一些我們這裡就不說了,有興趣的可以自己查閱一下

<version> 指協議版本,現在通常都是Http/1.1了

<request-url> 請求地址

<status> 指響應狀態碼, 我們熟悉的200、404等等

<reason-phrase> 原因短語,200 OK 、404 Not Found 這種後面的描述就是原因短語,通常不必太關注。

method

我們知道請求方法最常用的有Get 和Post兩種,面試時也常常會問到這兩者有什麼區別,通常什麼情況下使用。這裡我們來簡單說一說。

兩個方法之間在傳輸形式上有一些區別,通過Get方法發起請求時,會將請求參數拼接在request-url尾部,格式是url?param1=xxx&param2=xxx&[…]。

我們需要知道,這樣傳輸參數會使得參數都暴露在地址欄中。並且由於url是ASCII編碼的,所以參數中如果有Unicode編碼的字符,例如漢字,都會編碼之後傳輸。另外值得注意的是,雖然http協議並沒有對url長度做限制,但是一些瀏覽器和伺服器可能會有限制,所以通過GET方法發起的請求參數不能夠太長。而通過POST方法發起的請求是將參數放在請求體中的,所以不會有GET參數的這些問題。

另外一點差別就是方法本身的語義上的。GET方法通常是指從伺服器獲取某個URL資源,其行為可以看作是一個讀操作,對同一個URL進行多次GET並不會對伺服器產生什麼影響。而POST方法通常是對某個URL進行添加、修改,例如一個表單提交,通常會往伺服器插入一條記錄。多次POST請求可能導致伺服器的資料庫中添加了多條記錄。所以從語義上來講,兩者也是不能混為一談的。

狀態碼

常見的狀態碼主要有  
200 OK  請求成功,實體包含請求的資源  
301 Moved Permanent 請求的URL被移除了,通常會在Location首部中包含新的URL用於重定向。  
304 Not Modified    條件請求進行再驗證,資源未改變。  
404 Not Found       資源不存在  
206 Partial Content 成功執行一個部分請求。這個在用於斷點續傳時會涉及到。

header

在請求報文和響應報文中都可以攜帶一些信息,通過與其他部分配合,能夠實現各種強大的功能。這些信息位於起始行之下與請求實體之間,以鍵值對的形式,稱之為首部。每條首部以回車換行符結尾,最後一個首部額外多一個換行,與實體分隔開。

這裡我們重點關注一下  
Date  
Cache-Control  
Last-Modified  
Etag  
Expires  
If-Modified-Since  
If-None-Match  
If-Unmodified-Since  
If-Range  
If-Match

Http的首部還有很多,但限於篇幅我們不一一討論。這些首部都是Http緩存會涉及到的,在下文中我們會來說說各自的作用。

實體

請求發送的資源,或是響應返回的資源。

Http緩存

當我們發起一個http請求後,伺服器返回所請求的資源,這時我們可以將該資源的副本存儲在本地,這樣當再次對該url資源發起請求時,我們能快速的從本地存儲設備中獲取到該url資源,這就是所謂的緩存。緩存既可以節約不必要的網絡帶寬,又能迅速對http請求做出響應。

先擺出幾個概念:

新鮮度檢測

再驗證

再驗證命中

我們知道,有些url所對應的資源並不是一成不變的,伺服器中該url的資源可能在一定時間之後會被修改。這時本地緩存中的資源將與伺服器一側的資源有差異。

既然在一定時間之後可能資源會改變,那麼在某個時間之前我們可以認為這個資源沒有改變,從而放心大膽的使用緩存資源,當請求時間超過來該時間,我們認為這個緩存資源可能不再與伺服器端一致了。所以當我們發起一個請求時,我們需要先對緩存的資源進行判斷,看看究竟我們是否可以直接使用該緩存資源,這個就叫做新鮮度檢測。即每個資源就像一個食品一樣,擁有一個過期時間,我們吃之前需要先看看有沒有過期。

如果發現該緩存資源已經超過了一定的時間,我們再次發起請求時不會直接將緩存資源返回,而是先去伺服器查看該資源是否已經改變,這個就叫做再驗證。如果伺服器發現對應的url資源並沒有發生變化,則會返回304 Not Modified,並且不再返回對應的實體。這稱之為再驗證命中。相反如果再驗證未命中,則返回200OK,並將改變後的url資源返回,此時緩存可以更新以待之後請求。

我們看看具體的實現方式:

新鮮度檢測  
我們需要通過檢測資源是否超過一定的時間,來判斷緩存資源是否新鮮可用。那麼這個一定的時間怎麼決定呢?其實是由伺服器通過在響應報文中增加Cache-Control:max-age,或是Expire這兩個首部來實現的。值得注意的是Cache-Control是http1.1的協議規範,通常是接相對的時間,即多少秒以後,需要結合last-modified這個首部計算出絕對時間。而Expire是http1.0的規範,後面接一個絕對時間。

再驗證  
如果通過新鮮度檢測發現需要請求伺服器進行再驗證,那麼我們至少需要告訴伺服器,我們已經緩存了一個什麼樣的資源了,然後伺服器來判斷這個緩存資源到底是不是與當前的資源一致。邏輯是這樣沒錯。那怎麼告訴伺服器我當前已經有一個備用的緩存資源了呢?我們可以採用一種稱之為條件請求的方式實現再驗證。

Http定義了5個首部用於條件請求:  
If-Modified-Since  
If-None-Match  
If-Unmodified-Since  
If-Range  
If-Match

If-Modified-Since 可以結合Last-Modified這個伺服器返回的響應首部使用,當我們發起條件請求時,將Last-Modified首部的值作為If-Modified-Since首部的值傳遞到伺服器,意思是查詢伺服器的資源自從我們上一次緩存之後是否有修改。

If-None-Match 需要結合另一個Etag的伺服器返回的響應首部使用。Etag首部實際上可以認為是伺服器對文檔資源定義的一個版本號。有時候一個文檔被修改了,可能所做的修改極為微小,並不需要所有的緩存都重新下載數據。或者說某一個文檔的修改周期極為頻繁,以至於以秒為時間粒度的判斷已經無法滿足需求。這個時候可能就需要Etag這個首部來表明這個文檔的版號了。發起條件請求時可將緩存時保存下來的Etag的值作為If-None-Match首部的值發送至伺服器,如果伺服器的資源的Etag與當前條件請求的Etag一致,表明這次再驗證命中。  

其他三個與斷點續傳涉及到的相關知識有關,本文暫時不討論。待我之後寫一篇文章來講講斷點續傳。

OkHttp的緩存

緩存的Http理論知識大致就是這麼些。我們從OkHttp的源碼來看看,這些知名的開源庫是如何利用Http協議實現緩存的。這裡我們假設讀者對OkHttp的請求執行流程有了大致的了解,並且只討論緩存相關的部分。對於OkHttp代碼不熟悉的同學,建議先看看相關代碼或是其他文章。

我們知道OkHttp的請求在發送到伺服器之前會經過一系列的Interceptor,其中有一個CacheInterceptor即是我們需要分析的代碼。

 final InternalCache cache;

@Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    .

 }

方法首先通過InternalCache 獲取到對應請求的緩存。這裡我們不展開討論這個類的具體實現,只需要知道,如果之前緩存了該請求url的資源,那麼通過request對象可以查找到這個緩存響應。

將獲取到的緩存響應,當前時間戳和請求傳入CacheStrategy,然後通過執行get方法執行一些邏輯最終可以獲取到strategy.networkRequest,strategy.cacheResponse。如果通過CacheStrategy的判斷之後,我們發現這次請求無法直接使用緩存數據,需要向伺服器發起請求,那麼我們就通過CacheStrategy為我們構造的networkRequest來發起這次請求。我們先來看看CacheStrategy做了哪些事情。

CacheStrategy.Factory.java

public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }

CacheStrategy.Factory的構造方法首先保存了傳入的參數,並將緩存響應的相關首部解析保存下來。之後調用的get方法如下

    public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();

      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        
        return new CacheStrategy(null, null);
      }

      return candidate;
    }

get方法很簡單,主要邏輯在getCandidate中,這裡的邏輯是如果返回的candidate所持有的networkRequest不為空,表示我們這次請求需要發到伺服器,此時如果請求的cacheControl要求本次請求只使用緩存數據。那麼這次請求恐怕只能以失敗告終了,這點我們等會兒回到CacheInterceptor中可以看到。接著我們看看主要getCandidate的主要邏輯。

    private CacheStrategy getCandidate() {
      
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      
      
      
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }
        .
    }

上面這段代碼主要列出四種情況下需要忽略緩存,直接想伺服器發起請求的情況:

緩存本身不存在

請求是採用https 並且緩存沒有進行握手的數據。

緩存本身不應該不保存下來。可能是緩存本身實現有問題,把一些不應該緩存的數據保留了下來。

如果請求本身添加了 Cache-Control: No-Cache,或是一些條件請求首部,說明請求不希望使用緩存數據。

這些情況下直接構造一個包含networkRequest,但是cacheResponse為空的CacheStrategy對象返回。

    private CacheStrategy getCandidate() {
      .

      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {
        return new CacheStrategy(null, cacheResponse);
      }

      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();

      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }

      long maxStaleMillis = 0;
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }




      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());
      }

        .     
    }

如果緩存響應的Cache-Control首部包含immutable,那麼說明該資源不會改變。客戶端可以直接使用緩存結果。值得注意的是immutable並不屬於http協議的一部分,而是由facebook提出的擴展屬性。

之後分別計算ageMills、freshMills、minFreshMills、maxStaleMills這四個值。 
如果響應緩存沒有通過Cache-Control:No-Cache 來禁止客戶端使用緩存,並且

ageMillis + minFreshMillis < freshMillis + maxStaleMillis

這個不等式成立,那麼我們進入條件代碼塊之後最終會返回networkRequest為空,並且使用當前緩存值構造的CacheStrtegy。

這個不等式究竟是什麼含義呢?我們看看這四個值分別代表什麼。  

ageMills 指這個緩存資源自響應報文在源伺服器中產生或者過期驗證的那一刻起,到現在為止所經過的時間。用食品的保質期來比喻的話,好比當前時間距離生產日期已經過去了多久了。  

freshMills 表示這個資源在多少時間內是新鮮的。也就是假設保質期18個月,那麼這個18個月就是freshMills。 

minFreshMills 表示我希望這個緩存至少在多久之後依然是新鮮的。好比我是一個比較講究的人,如果某個食品只有一個月就過期了,雖然並沒有真的過期,但我依然覺得食品不新鮮從而不想再吃了。  

maxStaleMills 好比我是一個不那麼講究的人,即使食品已經過期了,只要不是過期很久了,比如2個月,那我覺得問題不大,還可以吃。

minFreshMills 和maxStatleMills都是由請求首部取出的,請求可以根據自己的需要,通過設置

Cache-Control:min-fresh=xxx、Cache-Control:max-statle=xxx

來控制緩存,以達到對緩存使用嚴格性的收緊與放鬆。

    private CacheStrategy getCandidate() {
        .

      
      
      String conditionName;
      String conditionValue;
      if (etag != null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        return new CacheStrategy(request, null); 
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

如果之前的條件不滿足,說明我們的緩存響應已經過期了,這時我們需要通過一個條件請求對伺服器進行再驗證操作。接下來的代碼比較清晰來,就是通過從緩存響應中取出的Last-Modified,Etag,Date首部構造一個條件請求並返回。

接下來我們返回CacheInterceptor

    
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

可以看到,如果我們返回的networkRequest和cacheResponse都為空,說明我們即沒有可用的緩存,同時請求通過Cache-Control:only-if-cached只允許我們使用當前的緩存數據。這個時候我們只能返回一個504的響應。接著往下看,

    
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

如果networkRequest為空,說明我們不需要進行再驗證了,直接將cacheResponse作為請求結果返回。

Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        
        
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }


     Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          
        }
      }
    }

    return response;

如果networkRequest存在不為空,說明這次請求是需要發到伺服器的。此時有兩種情況,一種cacheResponse不存在,說明我們沒有一個可用的緩存,這次請求只是一個普通的請求。如果cacheResponse存在,說明我們有一個可能過期了的緩存,此時networkRequest是一個用來進行再驗證的條件請求。

不管哪種情況,我們都需要通過networkResponse=chain.proceed(networkRequest)獲取到伺服器的一個響應。不同的只是如果有緩存數據,那麼在獲取到再驗證的響應之後,需要cache.update(cacheResponse, response)去更新當前緩存中的數據。如果沒有緩存數據,那麼判斷此次請求是否可以被緩存。在滿足緩存的條件下,將響應緩存下來,並返回。

OkHttp緩存大致的流程就是這樣,我們從中看出,整個流程是遵循了Http的緩存流程的。最後我們總結一下緩存的流程:

從接收到的請求中,解析出Url和各個首部。

查詢本地是否有緩存副本可以使用。

如果有緩存,則進行新鮮度檢測,如果緩存足夠新鮮,則使用緩存作為響應返回,如果不夠新鮮了,則構造條件請求,發往伺服器再驗證。如果沒有緩存,就直接將請求發往伺服器。

把從伺服器返回的響應,更新或是新增到緩存中。

OAuth

OAuth是一個用於授權第三方獲取相應資源的協議。與以往的授權方式不同的是,OAuth的授權能避免用戶暴露自己的用戶密碼給第三方,從而更加的安全。OAuth協議通過設置一個授權層,以區分用戶和第三方應用。用戶本身可以通過用戶密碼登陸服務提供商,獲取到帳戶所有的資源。而第三方應用只能通過向用戶請求授權,獲取到一個Access Token,用以登陸授權層,從而在指定時間內獲取到用戶授權訪問的部分資源。

OAuth定義的幾個角色:

RoleDescriptionResource Owner可以授權訪問某些受保護資源的實體,通常就是指用戶Client可以通過用戶的授權訪問受保護資源的應用,也就是第三方應用Authorization server在認證用戶之後給第三方下發Access Token的伺服器Resource Server擁有受保護資源的伺服器,可以通過Access Token響應資源請求

     +---+                               ++
     |        |--(A)- Authorization Request ->|   Resource    |
     |        |                               |     Owner     |
     |        |<-(B)-- Authorization Grant ---|               |
     |        |                               ++
     |        |
     |        |                               ++
     |        |--(C)-- Authorization Grant -->| Authorization |
     | Client |                               |     Server    |
     |        |<-(D) Access Token --|               |
     |        |                               ++
     |        |
     |        |                               ++
     |        |--(E) Access Token ->|    Resource   |
     |        |                               |     Server    |
     |        |<-(F)--- Protected Resource ---|               |
     +---+                               ++

從上圖可以看出,一個OAuth授權的流程主要可以分為6步:

客戶端向用戶申請授權。

用戶同意授權。

客戶端通過獲取的授權,向認證伺服器申請Access Token。

認證伺服器通過授權認證後,下發Access Token。

客戶端通過獲取的到Access Token向資源伺服器發起請求。

資源伺服器核對Access Token後下發請求資源。

Https

簡單的說 Http + 加密 + 認證 + 完整性保護 = Https

傳統的Http協議是一種應用層的傳輸協議,Http直接與TCP協議通信。其本身存在一些缺點:

Http協議使用明文傳輸,容易遭到竊聽。

Http對於通信雙方都沒有進行身份驗證,通信的雙方無法確認對方是否是偽裝的客戶端或者服務端。

Http對於傳輸內容的完整性沒有確認的辦法,往往容易在傳輸過程中被劫持篡改。

因此,在一些需要保證安全性的場景下,比如涉及到銀行帳戶的請求時,Http無法抵禦這些攻擊。  
Https則可以通過增加的SSL\TLS,支持對於通信內容的加密,以及對通信雙方的身份進行驗證。

Https的加密

近代密碼學中加密的方式主要有兩類:

對稱秘鑰加密

非對稱秘鑰加密

對稱秘鑰加密是指加密與解密過程使用同一把秘鑰。這種方式的優點是處理速度快,但是如何安全的從一方將秘鑰傳遞到通信的另一方是一個問題。

非對稱秘鑰加密是指加密與解密使用兩把不同的秘鑰。這兩把秘鑰,一把叫公開秘鑰,可以隨意對外公開。一把叫私有秘鑰,只用於本身持有。得到公開秘鑰的客戶端可以使用公開秘鑰對傳輸內容進行加密,而只有私有秘鑰持有者本身可以對公開秘鑰加密的內容進行解密。這種方式克服了秘鑰交換的問題,但是相對於對稱秘鑰加密的方式,處理速度較慢。

SSL\TLS的加密方式則是結合了兩種加密方式的優點。首先採用非對稱秘鑰加密,將一個對稱秘鑰使用公開秘鑰加密後傳輸到對方。對方使用私有秘鑰解密,得到傳輸的對稱秘鑰。之後雙方再使用對稱秘鑰進行通信。這樣即解決了對稱秘鑰加密的秘鑰傳輸問題,又利用了對稱秘鑰的高效率來進行通信內容的加密與解密。

Https的認證

SSL\TLS採用的混合加密的方式還是存在一個問題,即怎麼樣確保用於加密的公開秘鑰確實是所期望的伺服器所分發的呢?也許在收到公開秘鑰時,這個公開秘鑰已經被別人篡改了。因此,我們還需要對這個秘鑰進行認證的能力,以確保我們通信的對方是我們所期望的對象。

目前的做法是使用由數字證書認證機構頒發的公開秘鑰證書。伺服器的運營人員可以向認證機構提出公開秘鑰申請。認證機構在審核之後,會將公開秘鑰與共鑰證書綁定。伺服器就可以將這個共鑰證書下發給客戶端,客戶端在收到證書後,使用認證機構的公開秘鑰進行驗證。一旦驗證成功,即可知道這個秘鑰是可以信任的秘鑰。

總結
Https的通信流程:

Client發起請求

Server端響應請求,並在之後將證書發送至Client

Client使用認證機構的共鑰認證證書,並從證書中取出Server端共鑰。

Client使用共鑰加密一個隨機秘鑰,並傳到Server

Server使用私鑰解密出隨機秘鑰

通信雙方使用隨機秘鑰最為對稱秘鑰進行加密解密。

相關焦點

  • 學習SQL:MySQL必知必會
    《MySQL必知必會》這本書是2009年出版,從SQL語句的數據檢索、子查詢、聯結、正則表達式等內容講起,適用於沒有學過SQL以及剛開始接觸SQL
  • 2020年數據分析必知必會(八):使用pandas查詢數據和統計分析的應用(短小但強大)
    認認真真系統學習數據分析本文繼續學習Python數據分析知識,前期的知識點可點擊以下藍色字體連結進行回看複習:數據分析開篇:一個簡單的應用(2019/11/04)2020年數據分析必知必會(一):NumPy數組2020年數據分析必知必會(二):NumPy摘要----文章末尾附Python2020年數據分析必知必會(三):數組的形狀和屬性(有福利贈予)數據分析必知必必會(四):數組的轉換,視圖,拷貝,索引和廣播(這裡的「廣播」是一個數組的應用:數據處理舊手機鈴聲)2020年數據分析必知必會(五):統計和線性代數(使用Numpy
  • Git 必知必會《下》
    來源|微信公眾號 無量測試之道Git 必知必會
  • Android程式設計師必知必會的網絡通信傳輸層協議——UDP和TCP
    對於Android程式設計師來說,如果您覺得本文內容稍顯枯燥,可以看看即時通訊網之前整理過的一篇類似文章《邁向高階:優秀Android程式設計師必知必會的網絡基礎》,該文內容更偏向於知識點的概括。《腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識》《腦殘式網絡編程入門(四):快速理解HTTP/2的伺服器推送(Server Push)》《腦殘式網絡編程入門(五):每天都在用的Ping命令,它到底是什麼?》《腦殘式網絡編程入門(六):什麼是公網IP和內網IP?NAT轉換又是什麼鬼?》
  • 非謂語動詞入門必知,必會!
    非謂語動詞入門必知非謂語動詞可以說是英語語法當中的難點和重點,它涉及的內容比較多,很多英語學習者經常困惑,非常容易混淆他們之間的知識點。分開的話,就不能表示現在進行時了,而且,分開之後會造成句子的錯誤!為什麼呢?ing形式不可以做謂語動詞啊,咱們剛剛提到過哦。切記!我們常見的It is very kind of youto help me.
  • 數據分析必知必會(四):數組的轉換、視圖、拷貝、索引和廣播(這裡的「廣播」是一個數組的應用:數據處理舊手機鈴聲)
    本文開篇先介紹一下SciPy官網學習教程的網址:可下載DF學習http
  • 兩性必知:最完美性愛姿勢大全
    兩性養生必知:最完美的性愛姿勢大全(圖)  1.遊龍戲鳳  女子仰面向上躺臥,男子伏臥在對方身上,男股在女子兩腿中間。  取此勢交合,女子快感來臨時,由於全身為男人所束縛,雙腿又緊壓至胸前,此時欲宣洩快感情緒,必前後左右掙扎不停,這樣促使男人也跟著一起扭動身軀,再加上陽具左衝右刺,迎來送往,雙方在交合中均會享受到刺激與亢奮。在交合中,男人欣賞女子的深閨繡闈,步在九曲迴廊(陰道)之中,更可以感覺到女性
  • 【父母必知】兒童塗氟小科普
    【父母必知】兒童塗氟小科普 2020-12-22 14:57 來源:澎湃新聞·澎湃號·政務
  • creative destruction新手必知遊戲玩法和技巧匯總 關注遊戲中的...
    creative destruction新手必知遊戲玩法和技巧匯總 關注遊戲中的各種玩法時間:2018-06-25 18:05   來源:    責任編輯:凌君 川北在線核心提示:原標題:creativedestruction新手必知遊戲玩法和技巧匯總關注遊戲中的各種玩法 creativedestruction
  • 必知必會的育兒知識,讓寶寶即聰明又健康。
    三、寶寶的排尿習慣;有的媽媽為了讓寶寶早些擺拖紙尿褲,無論寶寶有沒有小便,到點就把寶寶弄醒,這樣會影響寶寶的睡眠質量,寶寶的排尿習慣不是人為訓練出來的,而是隨著生長發育慢慢形成的。五、不要把寶寶綁起來;1歲以內的小寶寶在床上睡覺,會擺出和青蛙一樣的姿勢,這是最可愛最自然的休息姿勢,有的寶媽就特別擔心,孩子會有羅圈腿什麼的,就用布把腿包起來,其實這樣寶寶休息不好,更會影響生長發育。我小時候,媽媽就給腿包了起來,老一輩都是這麼做的,現在是科學養育。
  • 面試必知必會|理解C++虛函數
    0.前言在後端面試中語言特性的掌握直接決定面試成敗,因此本公眾號在在後續會持續輸出程式語言的必知必會知識點系列這種做法的理論基礎是:一個派生類對象也是一個基類對象,可以將派生類對象看成基類對象,但是期間會發生隱式轉換。
  • 做人必知43條禮數
    做人必知禮數:陳淳《小學詩禮》選讀事親【其一】凡子事父母,雞鳴鹹盥漱。櫛總冠紳履,以適父母所。【其二】及所聲氣怡,燠寒問其衣。疾痛敬抑搔,出入敬扶持。【其三】將坐請何向,長席少執床。懸衾篋枕簟,灑掃室及堂。
  • 媽媽必知必會的育兒技巧,你知道嗎?
    0至6個月的小嬰兒千萬不要過度搖晃這樣會引起慢性的顱腦損傷而家長卻不自知。6個月以下小嬰兒的頭佔整個身體的比例較大,支撐頭部的頸部肌肉力量較弱,顱腦的發育尚不完善,上下左右搖晃、顫動都可能產生顱內損傷、血管破裂出血,甚至造成嚴重後遺症。
  • 【市民雲提醒】父母必知!關於兒童流感疫苗接種的那些事
    【市民雲提醒】父母必知!關於兒童流感疫苗接種的那些事 2020-09-24 15:30 來源:澎湃新聞·澎湃號·政務
  • 源野老師:自然拼讀法必知的變音,不要錯過,你會了嗎?
    源野老師:自然拼讀法必知的變音自然拼讀,難點也就是在元音詞組,和R音節的熟悉掌握度。剛開始接觸英語自然拼讀,不熟悉沒關係,後面多加的練習,強化訓練即可。源野老師:自然拼讀法必知的變音請看下面單詞spend [spend] speak [spik] space [spes]
  • 學英語翻譯必知的國內外語料庫
    2. BCC語料庫http://bcc.blcu.edu.cn/古漢語語料庫http://www.sinica.edu.tw/ftms-bin/ftmsw8. 近代漢語標記語料庫http://www.sinica.edu.tw/Early_Mandarin/9. 樹圖資料庫http://treebank.sinica.edu.tw/
  • 必知必會|C++學習中的核心知識點總結
    C++編譯器在 隱式類型轉換時會儘可能防止精度損失。強制類型轉換的一般格式為:cast-name(expression) ;其中cast-name是要強制轉換的方式,如static_cast;const_cast 等,type是欲轉換為的類型。儘量避免使用強制類型轉換。
  • @清遠家長 預約接種疫苗必知!
    @清遠家長 預約接種疫苗必知!該負責人表示,市民可在電話預約時告知工作人員,有幾個適齡接種兒童需要接種,工作人員登記後會根據實際情況安排預約接種。清遠發布編輯部來源:清遠+原標題:《@清遠家長 預約接種疫苗必知!》
  • 色女必知的男人身上敏感區 讓男人興奮的部位有哪些?
    色女必知的男人身上敏感區 讓男人興奮的部位有哪些?時間:2016-01-07 12:20   來源:鳳凰網   責任編輯:毛青青 川北在線核心提示:原標題:色女必知的男人身上敏感區 下面小編幫各位男性朋友總結一下女人身上最敏感的十個部位,如果你嘗試過後沒有效果,那各位就只能自己慢慢探索你的她身體裡的其他秘密通道了。
  • 關於 TCP/IP,必知必會的十個問題!
    本文整理了一些TCP/IP協議簇中需要必知必會的十大問題,既是面試高頻問題,又是程式設計師必備基礎素養。TCP/IP十個問題TCP/IP十個問題一、TCP/IP模型TCP/IP協議模型(Transmission Control Protocol/Internet Protocol),包含了一系列構成網際網路基礎的網絡協議,是Internet的核心協議。