天天看點

http協定無狀态中的 "狀态" 到底指的是什麼?!

引子:

最近在好好了解http,發現對介紹http的第一句話【http協定是無狀态的,無連接配接的】就無法了解了:無狀态的【狀态】到底指的是什麼?!

找了很多資料不僅沒有發現有一針見血正面回答這個問題的,而且有些解釋還充斥了各種錯誤,看着看着就覺得心裡憋着一股濁氣吐不出來

于是在看了很多資料之後,我一口吐出濁氣,大聲正面提出這個問題:http協定無狀态中的【狀态】到底指的是什麼?!

然後開始不斷探索解決這個問題。。。

最終很高興的是我找到了讓人滿意的答案,先賣個關子,各位如果着急可以直接拉到最下檢視

正文:http協定無狀态中的【狀态】到底指的是什麼?!

先來看這句話的另外兩個概念:(标準的http協定是無狀态的,無連接配接的)

  1. 标準的http協定指的是不包括cookies, session,application的http協定,他們都不屬于标準協定,雖然各種網絡應用提供商,實作語言、web容器等,都預設支援它
  2. 無連接配接指的是什麼
    1. 每一個通路都是無連接配接,伺服器挨個處理通路隊列裡的通路,處理完一個就關閉連接配接,這事兒就完了,然後處理下一個新的
    2. 無連接配接的含義是限制每次連接配接隻處理一個請求。伺服器處理完客戶的請求,并收到客戶的應答後,即斷開連接配接

對于【無狀态】,我看到很多隔着一層磨砂玻璃一樣的模糊說法(官方或者教程裡的說法),看着非常難受(但其實算是對的)(後來我發現我為什麼覺得它看着難受了,因為他們引入了很多新的,而且明顯是一個可能用在很多地方的廣義名詞,這些詞最大的作用就是,混淆概念,下面我标注了)

  1. 協定對于事務處理沒有記憶能力【事物處理】【記憶能力】
  2. 對同一個url請求沒有上下文關系【上下文關系】
  3. 每次的請求都是獨立的,它的執行情況和結果與前面的請求和之後的請求是無直接關系的,它不會受前面的請求應答情況直接影響,也不會直接影響後面的請求應答情況【無直接聯系】【受直接影響】
  4. 伺服器中沒有儲存用戶端的狀态,用戶端必須每次帶上自己的狀态去請求伺服器【狀态】

我必須得到确切而具體的解釋!

這幾點給了我下一步思考的方向:

  1. 【伺服器中沒有儲存用戶端的狀态,用戶端必須每次帶上自己的狀态去請求伺服器 】這裡的用戶端的狀态是不是确切地指伺服器沒有儲存客戶的資訊呢?但顯然不是啊
  2. 【HTTP無狀态的特性嚴重阻礙了這些應用程式的實作,畢竟互動是需要承前啟後的,簡單的購物車程式也要知道使用者到底在之前選擇了什麼商品】我對此質疑為什麼無狀态就不能實作購物車呢?伺服器就不能存儲東西了麼?
  3. 【 每次的請求都是獨立的,<它的執行情況和結果>與<前面的請求>和<之後的請求>是無直接關系的】我覺得這個說法比較靠譜,但是所謂的不同請求間的沒有關系,是指的請求内容沒有關系,還是隻是指請求本身沒有關系?
    1. 請求内容沒有關系隻可能是伺服器上不存有使用者資料才可能啊,但是顯然是存有的啊
    2. 請求本身沒有關系,這又有什麼意義呢,每一次的請求有什麼價值?

根據這個方向我做了一個模拟通路實驗:假如沒有cookie沒有session,隻有http的時候,那當一個注冊使用者通路這個購物網站的時候,會發生這些事情:

  1. 前提情況:
    1. 伺服器肯定為每個注冊使用者建立了資料表,記錄使用者的資料
    2. http是無連接配接的
  2. 第一步需要登入
    1. 使用者通過http把使用者的使用者名和密碼發送給伺服器,伺服器把他們跟自己存有的使用者資料對比,如果一緻,則傳回資訊登入成功
  3. 然後使用者點選某一商品頁
    1. 這個動作相當于輸入一個商品頁的網址
    2. 假如商品頁比較機密不對外公開,需要是使用者才能通路
    3. 而雖然http能傳送使用者名和密碼,而且剛才也輸入了,還驗證成功了,但是因為伺服器既不會記得你登入的狀态,你的用戶端也不會存儲你剛才輸入的使用者名和密碼
    4. 是以因為這一次通路因為無法确定你的身份,隻能通路失敗
      1. 這時候如果要解決這個問題,而且沒有cookie沒有session,那就隻能你在通路網址的同時繼續帶上你的使用者名和密碼(繼續輸入咯)其實就像我現在的APP一樣
  4. 假設上一步的問題解決了,就是每次通路的時候都會手動輸入使用者名和密碼,然後現在的情況是:你已經選了幾件商品在你的購物車中,你想再添加一件商品,于是你點選某個商品旁邊的加号
    1. 這個動作也相當于輸入一個網址,網址的内容是發送一個請求,往你的購物車中加入這個商品
    2. 系統首先用你傳來的使用者名和密碼驗證你的身份,然後通路你的資料庫,在其中的購物車屬性下加一條資料,就是這個商品的資料
    3. 操作結束後,傳回操作成功,并結束通路
  5. OK,實驗結束,看似沒有cookie沒有session也能湊合解決問題,其實兩個操作都有很大的問題
    1. 你每通路一次需要權限的内容都需要在用戶端輸入使用者名和密碼,這一項的繁瑣就不必贅述了
    2. 你的每一次操作都要與系統底層的資料庫進行互動
      1. 多次少量的通路存在非常大的性能浪費。非常容易就能想到肯定是一次大量的操作更加有效率,于是就想到了緩存區
    3. 你的非重要瑣碎資料也被寫進資料庫中,跟你的主要資料放在一起
      1. 一次次添加和删除購物車其實隻是跟你這次浏覽,或者叫這次會話有關,是臨時的資料,跟使用者的主要資訊無關,它們沒什麼價值,純粹的備援資料(不排除現在有的公司覺得這種資料也有非常大的價值可以讓它們巧妙的利用),用什麼存放這些臨時的資料,我們也很容易想到緩存區

經過這個模拟通路實驗,結合前面的思考方向,我們知道了三點:

  1. 伺服器上肯定存有使用者的資料,你送出的增删改查它也能夠處理,是以這句話中【伺服器中沒有儲存用戶端的狀态】的狀态并不是指使用者的資料,我們的猜測不對
  2. 我們的質疑對了,無狀态能實作購物車,可以通過伺服器上存有的使用者資料來實作
  3. 但是,使用上面這種方式實作購物車,存在三個比較大的問題。由此,我們不禁會想,這三個問題的解決是不是跟我們不确切了解的【狀态】一詞有關?于是,接下來我們來通過解決這三個問題來把【狀态】的意義探尋下去

由上所述,我們可以在http的基礎上增加一些機制來解決上面出現的三個問題

  1. 在使用者端增加一個記錄本是非常有必要的,正好官方加入的cookie機制跟這個一樣,它的用處也确實是上面讨論的那樣,一般就是用來辨別通路者的身份
  2. 在伺服器增加一個緩存區能同時解決後兩個問題
    1. 有了這個緩存區作為一個資料緩沖,就不用一次次地通路資料庫,浪費大量計算機資源,而是在最後統一歸入資料庫
    2. 有了這個緩存區,你就不用把臨時的資料放到資料庫中了,隻需要在你們交流告一段落之後,再把資料整理,把有用的資料歸入資料庫
  3. 這裡就自然引申出了一個重要的概念:會話,它作為一個緩沖存儲區被從資料庫中分離出來,理由并不生硬,它有其獨特的重要且不可替代的作用。這個東西恰好跟官方加入的session機制一樣
    1. 另外說一個非常具有迷惑性的容易讓人對session的主要作用産生偏離的了解:認為session存在的價值就是給通路者配置設定一個sessionID代替使用者名和密碼,
    2. 為什麼非常具有迷惑性,因為session确實做了這件事,而且也起到了很大的作用,是以它是對的,但是隻對一半,而且沒有涉及問題的本質,這種情況是最危險的(看似很有說服力,把你說服了,是以你很難有動力繼續找下去,但是真實情況跟它有偏差,但是偏差不大,是以又很難把你說服回來,隻有隐隐的不對勁,這個時候你離真實最近,也離真實最遠)
    3. 那就順便說說它為什麼是對的,也就是用session做的另一件有用的事:
      1. 給每個session一個ID,一方面用來友善自己查詢,另一方面把這個ID給使用者,使用者下一次通路的時候就可以不用使用者名和密碼,而是直接使用這個ID來表明自己的身份
      2. 首先,這個ID安全嗎?這個ID比直接傳使用者名和密碼安全嗎?
        1. 你很容易會想到,本來使用者名和密碼的組合還特地設定地比較複雜,你這換一組數字就代替了,是不是太不安全了?
        2. 我們知道http協定本身是完全不加密的,如果使用使用者名和密碼,第一次通路是放在http頭中,後邊自動儲存了密碼就會放在cookie中,這些都完全沒有加密,它的安全性基本為0,就是裸奔了,隻要被竊取,那就丢失了
        3. 是以,就這個意義來講,sessionID的安全性跟使用使用者名和密碼沒什麼差別
        4. 但是其實,雖然http本身不能加密,但是有些軟體什麼的,能在應用層面手動給你加密,比如QQ就會使使用者名密碼加臨時驗證碼聯合哈希,sessionID加一個時間戳簡單加密也是非常常用的方法
        5. 而且因為sessionID本身有有效期,即使丢了,也可能很快失效,造成的損失可能沒那麼大,而使用者名跟密碼丢了,那就大了
        6. 是以總結就是:
          1. 不嚴格加密的sessionID和使用者名和密碼一樣,都不太安全
          2. 但是相比較來說,sessionID要安全一些
          3. 而使用https是完全安全的
      3. 然後,使用sessionID有哪些好處
        1. 友善直接根據ID查詢使用者對應的session
        2. 加密的時候計算量小
        3. 安全性不會降低,甚至還更高一些

OK,通過獨立地解決純http機制會産生的問題,我們探讨了cookie和session機制的本質。而且想到:【使用http協定,伺服器中不會儲存用戶端的狀态】所産生的問題通過增加cookie和session機制解決了,是不是就意味着這個【狀态】跟cookie和session的關系非常緊密?是以這個無狀态指的是【沒有對 本次會話 設定一個緩存區,記錄這次會話的狀态,緩存區包括伺服器端和使用者端】但好像還是沒有點破關鍵(主要是覺得跟前面那些官方對狀态的說法不太吻合,甚至沒有對應關系)

忽然我想到一個問題:一個有狀态的http是什麼樣的?

  1. 很難直接想象有狀态的http是什麼樣,因為http這種機制是天然無狀态的
  2. 那就類比一下吧,另一個天然有狀态的機制叫TCP
    1. 如果有狀态的意思是它的每次請求是有聯系的,那麼有狀态的TCP的樣子是:假如一份資料分了三份TCP包發送,那這個包上面會标明這是第幾個包,會标明這個包跟那幾個包是有聯系的,有什麼聯系
  3. 但好像這個有狀态的TCP跟我們想要的有狀态的HTTP沒有關系,因為即使每次http請求之間互相有聯系,它也不能解決上面提到的http無狀态的問題
  4. 诶,等等,好像能類比:
    1. 假如每個http連接配接都有一個簽名,于是第一次登陸成功之後,伺服器就知道了這個簽名是允許登陸的,于是之後所有同樣簽名的http連接配接都能登陸,這裡利用了同一個使用者發出的http連接配接之間的同主人關系,這裡解決了一個保持登入狀态的問題
    2. 同樣,來嘗試利用這個【每次http請求之間互相有聯系】來解決上面碰到的那個問題【每一次操作都要與系統底層的資料庫進行互動】,但想了半天确實無法進行下去
    3. 不過我靈機一動,從另一個角度來想,好像解決了這個問題:
      1. 隻有【每次http請求之間互相有聯系】這個條件,無法解決【每一次操作都要與系統底層的資料庫進行互動】
      2. 因為很明顯,要解決【每一次操作都要與系統底層的資料庫進行互動】就必須在伺服器端開辟一塊緩存區
      3. 不過如果你思考一下如何實作【每次http請求之間互相有聯系】,你就會發現,它也需要在伺服器端開辟一塊緩存區
      4. 是以【在伺服器端開辟一塊緩存區】才是真正的條件,也就是說,它确實等價于【有狀态】
      5. 而且我也找到了這個【在伺服器端開辟一塊緩存區】的條件跟前面那些官方對狀态的說法對應的點,那就是:
        1. 通過在伺服器端開辟一塊緩存區,存儲、記憶、共享一些臨時資料,你就可以:
          1. 協定對于事務處理有記憶能力【事物處理】【記憶能力】
          2. 對同一個url請求有上下文關系【上下文關系】
          3. 每次的請求都是不獨立的,它的執行情況和結果與前面的請求和之後的請求是直接關系的【不獨立】【直接關系】
          4. 伺服器中儲存用戶端的狀态【狀态】
      6. 是以,這個狀态,加上前面說的用戶端也有cookie,就是指,用戶端和伺服器在臨時會話中産生的資料!而前面也說道了,使用緩存區儲存臨時會話中的資料是多麼重要
        1. 是以狀态不僅包括不同URL通路之間的關系,還有對其他URL通路的資料記錄,還有一些其他的東西,是以更确切地說,狀态應該是【實作了這些東西所憑借的後面的緩存空間】中的客戶的臨時資料
        2. cookie和session應該是完全實作了有狀态這個功能

一種常見的對狀态的誤解:

  1. 有人在解釋HTTP的無狀态時,把它跟有連接配接對立,說是兩種方式,也就是如果想不無狀态,就必須有連接配接,但其實不然
  2. 有連接配接和無連接配接以及之後的Keep-Alive都是指TCP連接配接
  3. 有狀态和無狀态可以指TCP也可以指HTTP
  4. TCP一直有狀态,HTTP一直無狀态,但是應用為了有狀态,就給HTTP加了cookie和session機制,讓使用http的應用也能有狀态,但http還是無狀态
  5. 開始TCP是有連接配接,後來TCP無連接配接,再後來也就是現在TCP是Keep-Alive,有點像有連接配接