本篇文章原文出處爲:賽艇隊長的《http協議無狀態中的 "狀態" 到底指的是什麼?!》。這篇文章只是筆者在看賽艇隊長的文章時所作的筆記。所謂筆記就是對他人的文章閱讀、消化後,加上本身的一些理解所寫而成。固然要看原汁原味的文章,仍是推薦讀者看文章的原文。做者的思路很好。html
只是做者認爲HTTP是無鏈接的,而我我的認爲HTTP1.0是短連接的。web
一、http協議無狀態中的【狀態】到底指的是什麼?!
先來看這句話的另外兩個概念:(標準的http協議是無狀態的,短鏈接的)。
這裏的無短鏈接指的是http1.0協議。http1.1中加入了Connection:keep-alive,實現了http的長連接。
- 標準的http協議指的是不包括cookies, session,application的http協議,他們都不屬於標準協議,雖然各類網絡應用提供商,實現語言、web容器等,都默認支持它。
- 短鏈接指的是什麼
-
- 每個訪問都是短鏈接,服務器挨個處理訪問隊列裏的訪問,處理完一個就關閉鏈接,這事兒就完了,而後處理下一個新的。
- 每個 HTTP 請求都由它本身獨立的鏈接完成;這意味着發起每個 HTTP 請求以前都會有一次 TCP 握手,並且是接二連三的。
- TCP 協議握手自己就是耗費時間的,因此 TCP 能夠保持更多的熱鏈接來適應負載。短鏈接破壞了 TCP 具有的能力,新的冷鏈接下降了其性能。
對於【無狀態】,我看到不少隔着一層磨砂玻璃同樣的模糊說法(官方或者教程裏的說法),看着很是難受(但其實算是對的)(後來我發現我爲何以爲它看着難受了,由於他們引入了不少新的,並且明顯是一個可能用在不少地方的廣義名詞,這些詞最大的做用就是,混淆概念,下面我標註了)
- 協議對於事務處理沒有記憶能力【事物處理】【記憶能力】
- 對同一個url請求沒有上下文關係【上下文關係】
- 每次的請求都是獨立的,它的執行狀況和結果與前面的請求和以後的請求是無直接關係的,它不會受前面的請求應答狀況直接影響,也不會直接影響後面的請求應答狀況【無直接聯繫】【受直接影響】
- 服務器中沒有保存客戶端的狀態,客戶端必須每次帶上本身的狀態去請求服務器【狀態】
這幾點給了我下一步思考的方向:
- 【服務器中沒有保存客戶端的狀態,客戶端必須每次帶上本身的狀態去請求服務器 】這裏的客戶端的狀態是否是確切地指服務器沒有保存客戶的信息呢?但顯然不是啊
- 【HTTP無狀態的特性嚴重阻礙了這些應用程序的實現,畢竟交互是須要承前啓後的,簡單的購物車程序也要知道用戶到底在以前選擇了什麼商品】我對此質疑爲何無狀態就不能實現購物車呢?服務器就不能存儲東西了麼?
- 【 每次的請求都是獨立的,<它的執行狀況和結果>與<前面的請求>和<以後的請求>是無直接關係的】我以爲這個說法比較靠譜,可是所謂的不一樣請求間的沒有關係,是指的請求內容沒有關係,仍是隻是指請求自己沒有關係?
- 請求內容沒有關係只多是服務器上不存有用戶數據纔可能啊,可是顯然是存有的啊
- 請求自己沒有關係,這又有什麼意義呢,每一次的請求有什麼價值?
根據這個方向我作了一個模擬訪問實驗:假如沒有cookie沒有session,只有http的時候,那當一個註冊用戶訪問這個購物網站的時候,會發生這些事情:
- 前提狀況:
- 服務器確定爲每一個註冊用戶創建了數據表,記錄用戶的數據
- http是無鏈接的
- 第一步須要登陸
- 用戶經過http把用戶的用戶名和密碼發送給服務器,服務器把他們跟本身存有的用戶資料對比,若是一致,則返回信息登陸成功
- 而後用戶點擊某一商品頁
- 這個動做至關於輸入一個商品頁的網址
- 假如商品頁比較機密不對外公開,須要是用戶才能訪問
- 而雖然http能傳送用戶名和密碼,並且剛纔也輸入了,還驗證成功了,可是由於服務器既不會記得你登陸的狀態,你的客戶端也不會存儲你剛纔輸入的用戶名和密碼
- 因此由於這一次訪問由於沒法肯定你的身份,只能訪問失敗
- 這時候若是要解決這個問題,並且沒有cookie沒有session,那就只能你在訪問網址的同時繼續帶上你的用戶名和密碼(繼續輸入咯)其實就像我如今的APP同樣
- 假設上一步的問題解決了,就是每次訪問的時候都會手動輸入用戶名和密碼,而後如今的狀況是:你已經選了幾件商品在你的購物車中,你想再添加一件商品,因而你點擊某個商品旁邊的加號
- 這個動做也至關於輸入一個網址,網址的內容是發送一個請求,往你的購物車中加入這個商品
- 系統首先用你傳來的用戶名和密碼驗證你的身份,而後訪問你的數據庫,在其中的購物車屬性下加一條數據,就是這個商品的數據
- 操做結束後,返回操做成功,並結束訪問
- OK,實驗結束,看似沒有cookie沒有session也能湊合解決問題,其實兩個操做都有很大的問題
- 你每訪問一次須要權限的內容都須要在客戶端輸入用戶名和密碼,這一項的繁瑣就沒必要贅述了
- 你的每一次操做都要與系統底層的數據庫進行交互
- 屢次少許的訪問存在很是大的性能浪費。很是容易就能想到確定是一次大量的操做更加有效率,因而就想到了緩存區
- 你的非重要瑣碎數據也被寫進數據庫中,跟你的主要數據放在一塊兒
- 一次次添加和刪除購物車其實只是跟你此次瀏覽,或者叫此次會話有關,是臨時的數據,跟用戶的主要信息無關,它們沒什麼價值,純粹的冗餘數據(不排除如今有的公司以爲這種數據也有很是大的價值可讓它們巧妙的利用),用什麼存放這些臨時的數據,咱們也很容易想到緩存區
通過這個模擬訪問實驗,結合前面的思考方向,咱們知道了三點:
- 服務器上確定存有用戶的數據,你提交的增刪改查它也可以處理,因此這句話中【服務器中沒有保存客戶端的狀態】的狀態並非指用戶的數據,咱們的猜想不對
- 咱們的質疑對了,無狀態能實現購物車,能夠經過服務器上存有的用戶數據來實現
- 可是,使用上面這種方式實現購物車,存在三個比較大的問題。由此,咱們不由會想,這三個問題的解決是否是跟咱們不確切瞭解的【狀態】一詞有關?因而,接下來咱們來經過解決這三個問題來把【狀態】的意義探尋下去
由上所述,咱們能夠在http的基礎上增長一些機制來解決上面出現的三個問題
- 在用戶端增長一個記錄本是很是有必要的,正好官方加入的cookie機制跟這個同樣,它的用處也確實是上面討論的那樣,通常就是用來標識訪問者的身份
- 在服務器增長一個緩存區能同時解決後兩個問題
- 有了這個緩存區做爲一個數據緩衝,就不用一次次地訪問數據庫,浪費大量計算機資源,而是在最後統一納入數據庫
- 有了這個緩存區,你就不用把臨時的數據放到數據庫中了,只須要在大家交流告一段落以後,再把數據整理,把有用的數據納入數據庫
- 這裏就天然引伸出了一個重要的概念:會話,它做爲一個緩衝存儲區被從數據庫中分離出來,理由並不生硬,它有其獨特的重要且不可替代的做用。這個東西剛好跟官方加入的session機制同樣
- 另外說一個很是具備迷惑性的容易讓人對session的主要做用產生偏離的理解:認爲session存在的價值就是給訪問者分配一個sessionID代替用戶名和密碼,
- 爲何很是具備迷惑性,由於session確實作了這件事,並且也起到了很大的做用,因此它是對的,可是隻對一半,並且沒有涉及問題的本質,這種狀況是最危險的(看似頗有說服力,把你說服了,因此你很難有動力繼續找下去,可是真實狀況跟它有誤差,可是誤差不大,因此又很難把你說服回來,只有隱隱的不對勁,這個時候你離真實最近,也離真實最遠)
- 那就順便說說它爲何是對的,也就是用session作的另外一件有用的事:
- 給每一個session一個ID,一方面用來方便本身查詢,另外一方面把這個ID給用戶,用戶下一次訪問的時候就能夠不用用戶名和密碼,而是直接使用這個ID來代表本身的身份
- 首先,這個ID安全嗎?這個ID比直接傳用戶名和密碼安全嗎?
- 你很容易會想到,原本用戶名和密碼的組合還特意設置地比較複雜,你這換一組數字就代替了,是否是太不安全了?
- 咱們知道http協議自己是徹底不加密的,若是使用用戶名和密碼,第一次訪問是放在http頭中,後邊自動保存了密碼就會放在cookie中,這些都徹底沒有加密,它的安全性基本爲0,就是裸奔了,只要被竊取,那就丟失了
- 因此,就這個意義來說,sessionID的安全性跟使用用戶名和密碼沒什麼區別
- 可是其實,雖然http自己不能加密,可是有些軟件什麼的,能在應用層面手動給你加密,好比QQ就會使用戶名密碼加臨時驗證碼聯合哈希,sessionID加一個時間戳簡單加密也是很是經常使用的方法
- 並且由於sessionID自己有有效期,即便丟了,也可能很快失效,形成的損失可能沒那麼大,而用戶名跟密碼丟了,那就大了
- 因此總結就是:
- 不嚴格加密的sessionID和用戶名和密碼同樣,都不太安全
- 可是相比較來講,sessionID要安全一些
- 而使用https是徹底安全的
- 而後,使用sessionID有哪些好處
- 方便直接根據ID查詢用戶對應的session
- 加密的時候計算量小
- 安全性不會下降,甚至還更高一些
OK,經過獨立地解決純http機制會產生的問題,咱們探討了cookie和session機制的本質。並且想到:【使用http協議,服務器中不會保存客戶端的狀態】所產生的問題經過增長cookie和session機制解決了,是否是就意味着這個【狀態】跟cookie和session的關係很是緊密?因此這個無狀態指的是【
沒有對 本次會話 設置一個緩存區,記錄此次會話的狀態,緩存區包括服務器端和用戶端】但好像仍是沒有點破關鍵(主要是以爲跟前面那些官方對狀態的說法不太吻合,甚至沒有對應關係)
突然我想到一個問題:一個有狀態的http是什麼樣的?
- 很難直接想象有狀態的http是什麼樣,由於http這種機制是自然無狀態的
- 那就類比一下吧,另外一個自然有狀態的機制叫TCP
- 若是有狀態的意思是它的每次請求是有聯繫的,那麼有狀態的TCP的樣子是:假如一份數據分了三份TCP包發送,那這個包上面會標明這是第幾個包,會標明這個包跟那幾個包是有聯繫的,有什麼聯繫
- 但好像這個有狀態的TCP跟咱們想要的有狀態的HTTP沒有關係,由於即便每次http請求之間互相有聯繫,它也不能解決上面提到的http無狀態的問題
- 誒,等等,好像能類比:
- 假如每一個http鏈接都有一個簽名,因而第一次登錄成功以後,服務器就知道了這個簽名是容許登錄的,因而以後全部一樣簽名的http鏈接都能登錄,這裏利用了同一個用戶發出的http鏈接之間的同主人關係,這裏解決了一個保持登陸狀態的問題
- 一樣,來嘗試利用這個【每次http請求之間互相有聯繫】來解決上面碰到的那個問題【每一次操做都要與系統底層的數據庫進行交互】,但想了半天確實沒法進行下去
- 不過我靈機一動,從另外一個角度來想,好像解決了這個問題:
- 只有【每次http請求之間互相有聯繫】這個條件,沒法解決【每一次操做都要與系統底層的數據庫進行交互】
- 由於很明顯,要解決【每一次操做都要與系統底層的數據庫進行交互】就必須在服務器端開闢一塊緩存區
- 不過若是你思考一下如何實現【每次http請求之間互相有聯繫】,你就會發現,它也須要在服務器端開闢一塊緩存區
- 因此【在服務器端開闢一塊緩存區】纔是真正的條件,也就是說,它確實等價於【有狀態】
- 並且我也找到了這個【在服務器端開闢一塊緩存區】的條件跟前面那些官方對狀態的說法對應的點,那就是:
- 經過在服務器端開闢一塊緩存區,存儲、記憶、共享一些臨時數據,你就能夠:
- 協議對於事務處理有記憶能力【事物處理】【記憶能力】
- 對同一個url請求有上下文關係【上下文關係】
- 每次的請求都是不獨立的,它的執行狀況和結果與前面的請求和以後的請求是直接關係的【不獨立】【直接關係】
- 服務器中保存客戶端的狀態【狀態】
- 因此,這個狀態,加上前面說的客戶端也有cookie,就是指,客戶端和服務器在臨時會話中產生的數據!而前面也說道了,使用緩存區保存臨時會話中的數據是多麼重要
- 因此狀態不只包括不一樣URL訪問之間的關係,還有對其餘URL訪問的數據記錄,還有一些其餘的東西,因此更確切地說,狀態應該是【實現了這些東西所憑藉的後面的緩存空間】中的客戶的臨時數據
- cookie和session應該是徹底實現了有狀態這個功能
一種常見的對狀態的誤解:
- 有人在解釋HTTP的無狀態時,把它跟有鏈接對立,說是兩種方式,也就是若是想不無狀態,就必須有鏈接,但其實否則
- 有鏈接和無鏈接以及以後的Keep-Alive都是指TCP鏈接。只是因爲http協議的限制,使得每發出一個請求,TCP鏈接都必須先創建一次鏈接,當接受到響應後就通知TCP斷開鏈接。
- 有狀態和無狀態能夠指TCP也能夠指HTTP
- TCP一直有狀態,HTTP一直無狀態,可是應用爲了有狀態,就給HTTP加了cookie和session機制,讓使用http的應用也能有狀態,但http仍是無狀態。
二、小結
因此說,要想讓HTTP應用有狀態,須要引入Cookie和Session機制。數據庫
Cookie是存儲在客戶端上的肯定用戶的標識,可是隻有客戶端有標識是沒有用的。肯定身份是須要兩方共同認可才能正確的肯定身份。因此在服務器端經過Session來肯定用戶標識。緩存
客戶端的Cookie中存放着服務器端的Session中對應的SessionID,Session依據該Cookie來識別是否爲同一用戶。可是Session中不只存着對應的用戶ID,對應的緩存區中還存儲着臨時會話產生的數據,在會話操做完成時統一存入數據庫。安全
注意:咱們講的都是Cookie中存放的是Session ID,而不是用戶的用戶名和密碼。由於直接存放用戶的密碼是極其不安全的,由於Cookie是放在HTTP協議頭上傳送的,而HTTP協議是明文傳輸的。很容易被別人竊取。有的人說對密碼加密不就好了,可是加密的密碼也可能會被破解,何況別人竊取了包含你密碼的Cookie並不須要破解你的密碼呀,只須要將這個Cookie用來直接登錄不就好了。服務器