HTTP #web服務器 #三次握手四次揮手 #多進程 #多線程 #多協程 #非阻塞 #epoll #網絡通信

一、HTTP是什麼

  • http就是超文本傳輸協議 ,它基於TCP。是瀏覽器和服務器之間用於傳輸的一種規定,請求的時候按照什麼什麼發,回的時候按照什麼什麼發,它們都是基於TCP發送的二進制數據。現今,HTTP語句運用到了不在瀏覽器的方面。
  • 瀏覽器一定是客戶端
    在這裏插入圖片描述
  • 這一坨綠油油的就是HTTP協議。具體解釋如下:
GET /happy.html HTTP/1.1  # 請求方式Get,請求文件happy.html,HTTP1.1版本
# 下面的東西,類似於字典(其實是字符串)
Host: 127.0.0.1:8080  # 主機端口
Connection: keep-alive  # 長鏈接
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)   AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36  # 客戶端瀏覽器版本
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3  # 瀏覽器能接收的格式
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br  # 能接收的壓縮格式
Accept-Language: zh-CN,zh;q=0.9  # 能接收的語言
  • 查看HTTP協議
  1. 如圖操作
    在這裏插入圖片描述
  2. 點擊請求和應答後面的View Source
  • 這就是瀏覽器發送的請求
    在這裏插入圖片描述
  • 這就是瀏覽器接收的應答
    在這裏插入圖片描述
    一去一回就是HTTP協議。
    服務器返回的東西中,上面那個應答的部分,被稱爲應答頭head。HTTP/1.1 200 OK是其中必不可少的。後面緊跟傳輸的內容body,是瀏覽器真正傳輸的東西。第一個空行上方是head,下方是body。
  • 我們用網絡調試助手,充當服務器給瀏覽器法信息。(需要有一個返回頭,和一串信息。)
HTTP/1.1 200 OK

<h1>hello world</h1>

在這裏插入圖片描述

二、Web服務器

1.寫代碼框架

  • 既然網絡調試助手可以當服務器,那麼之前寫的python程序也可以
  • 準備一個html文件
  • 代碼如下:
    在這裏插入圖片描述
  • 效果如下:
    在這裏插入圖片描述

2.TCP的三次握手、四次揮手

2.1三次握手

  • 三次握手就是雙方準備資源
  • connect默認堵塞,在三次握手成功時,解堵塞。也就是說,握手的起點是connect。
  1. 客戶端:你準備好了嗎?

客戶端給服務器發送一個數字。
爲了顯示是第一次發送的請求,前面用syn標記

  1. 服務器:我好了,你呢?

服務器將這個數字+1返回,用ack標記
並返回一個新的值,用syn標記

  1. 客戶端:愉快地開始玩耍吧

客戶端將這個新的值+1返回,用ack標記
在這裏插入圖片描述

2.2四次揮手

  • 四次揮手就是雙方釋放資源
  • TCP是全雙工的,必須得收發都關了,纔是真正的關閉。
  • 揮手的起點是close。
  1. 客戶端:我他娘地以後不再跟你講一句話。(客戶端關閉發送)

客戶端不和服務器說話時,服務器的 client_socket.recv()會堵塞。可是這次客戶端如此決絕,服務器就解堵塞。

recv_data = client_socket.recv()

if recv_data:
   """來欣賞妾身的舞姿"""
else:
   """滾吧,渣男"""
   client_socket.close()
  1. 服務器:好吧,隨你(服務器確認接收到客戶端的信息,客戶端關閉接收)
  2. 服務器:我也不理你了,滾吧,渣男(服務器關閉對他的發送)
  3. 客戶端:fine o( ̄︶ ̄)o(客戶端關閉接收)
    在這裏插入圖片描述

a. 客戶端調用close(),客戶端關閉發送
b. 服務器給客戶端迴應自己收到了。
c. 服務器的client_socket.recv()還在堵塞。如果加上一個判斷其的條件語句,當收到空時,也就是客戶端執行了close()操作時,服務器解堵塞。服務器關閉對中國客戶端的發送。
e. 客戶端知道服務器不給自己發數據時,客戶端就關閉了收數據。並且向服務器發送一個確認的包。

  • 但問題是: 客戶端怎麼知道服務器收到了?答,返回一個確認包。怎麼確認對方收到了返回的確認包?對方再返回一個確認確認的確認包…這不就是死循環嗎?
  • 解決辦法: 規定,誰先調用的最後一次Close,誰就等待一個時間,如果超過這個時間沒收到確認包,就再發一個。於是!接收包的這方(在這裏是C),回覆一個確認包後,要等待兩份的這個時間,記做2MSL,如果對方沒收到確認包,它需要再次接收一次確認包。
  • 注意: 誰先發起close,誰就要等待。所以要客戶端先發。不然服務器強制關閉後,會出現某某端口被佔用。(客戶端端口不固定換就完事,服務器是固定端口,所以會出問題)
  • 添加這一行代碼,就可以在調用close後,立馬結束進程
    在這裏插入圖片描述

3.應用戶需求打開頁面

  1. 獲取用戶需求的頁面名
  1. 切割發送的請求
  2. 用re模塊,獲取頁面名
  1. 爲了防止頁面名爲空,設定一個主頁index.html
  2. 打開相應文件
  • 代碼:
    在這裏插入圖片描述

4.完善代碼

  • 有一個小BUG:輸入不存在的文件,應該返回404
    在這裏插入圖片描述

三、併發HTTP服務器

1.多進程實現

在這裏插入圖片描述
在這裏插入圖片描述

  • 關於爲什麼要對客戶端套接字調用兩次close:

主進程創建套接字後,由於子進程會複製一模一樣的資源,所以對這個套接字就會有兩個硬鏈接。
主進程調用close(),並不會把這個套接字釋放,只會減少一個硬鏈接。子進程中的代碼執行完時,調用的close(),纔會真正四次揮手。

2.多線程實現

  • 對剛纔多進程文件

:%s/multiprocessing/threading/g
:%s/Process/Thread/g

  • 刪除主進程裏的close()

多線程並不存在有兩個硬鏈接的問題,當主進程調用close時,就已經被關閉了。

在這裏插入圖片描述
在這裏插入圖片描述

3.多協程實現

在這裏插入圖片描述
在這裏插入圖片描述

4.單進程單線程非阻塞

  • 套接字有個.setblocking(False)可以設置爲非堵塞。那麼哪裏會阻塞呢?沒有新客戶端接入和未收到信息會阻塞。
  • 由於非堵塞時,未接受到一定會報錯,所以使用try結構。下面我們用一種取巧的方式驗證(沒有就輸入0)
while True:
    try:
        new_socket = int(input("請輸入一個新的套接字"))
        zero_s = 5/new_socket
    except Exception as ret:
        print(">>>>>>>沒有新的客戶端<<<<<<<")
    else:
        print("-------新客戶端-------")
        try:
            recv = int(input("請輸入接收的消息"))
            zero_c = 3/recv
        except Exception as ret:
            print("]]]]]]]沒有接收的消息[[[[[[[")
        else:
            print("=======新消息=======")
  • 結果驗證:

在這裏插入圖片描述
在這個套接字未接收到消息後,是不會繼續服務它的。所以放在外部。可以用個列表存放套接字,在需要時取用。

  • 最終代碼:
    在這裏插入圖片描述
  • 如果發消息過快,會發現消息有可能會在同一行內,說明?

說明電腦有一個緩存區,消息是放在緩存區裏的。需要時再取用。進程每隔一段時間來看有沒有它的數據,沒有的話,要麼阻塞要麼異常。

5.長鏈接&短鏈接

  • HTTP1.0時期是短鏈接,現在的HTTP1.1是長鏈接
  • 爲了獲取一個對象的三個數據可以有兩種方式
  • 短鏈接
  1. 三次握手,接收第一個數據,四次揮手
  2. 三次握手,接收第二個數據,四次揮手
  3. 三次握手,接收第三個數據,四次揮手
  • 長鏈接:
  1. 三次握手,接收第一個數據
  2. 接收第二個數據
  3. 接收第三個數據,四次揮手

5.2非阻塞實現長鏈接

  • close關閉服務套接字,那不就相當於短鏈接了嗎?
  • 註釋掉close,瀏覽器就會一直轉圈圈。(因爲不知道接收完數據了)
  • 可以用Content-Length請求頭,並獲取文件體長度,只要長度達到了,瀏覽器就知道接收完數據了。
    在這裏插入圖片描述
    在這裏插入圖片描述
  • 代碼實現:
    在這裏插入圖片描述
    在這裏插入圖片描述

6.epoll

  • epoll是當今Linux中採用的方式。比如gevent底層的實現就是用epoll。
  • nginx和apache都是用的epoll。
  • 這部分了解怎麼用就行。
  • epoll是單進程單線程的。
  • 操作系統有內存,應用程序也有內存。兩部分內存相互獨立。
  • 單線程非阻塞,是在應用程序內部有一個服務套接字列表。每當for循環(輪詢)到某一個套接字時,會複製這個對象的fd(文件索引),然後給操作系統內存。操作系統就會查看是否有屬於它的數據,沒有就異常或阻塞。因爲存在複製過程,列表越大,性能就會顯著降低。
  • epoll可以讓應用程序和操作系統內核共用某一塊內存。並且不遍歷了,以事件通知的方式。

6.2epoll代碼實現

  1. 導入select,創建一個共享內存,並將監聽套接字寫入
    在這裏插入圖片描述
  2. 返回觸發事件的列表
    在這裏插入圖片描述
  3. 當新客戶端連接時,生成一個新套接字
    在這裏插入圖片描述
  4. 當服務套接字接收到信息時、斷開時
    在這裏插入圖片描述
  • 最終代碼:
    在這裏插入圖片描述
    在這裏插入圖片描述

四、網絡通信

1.TCP/IP協議

  • tcp-ip協議是一類協議的簡稱:
    在這裏插入圖片描述
  • 鏈路層→網路層→傳輸層→應用層
  • 應用層: 包括應用自己規定的協議,瀏覽器之類的還有HTTP協議
  • 傳輸層: 一個應用可以既走TCP又走UDP,並且端口可以一致。(TCP內部不能存在兩個8090端口,UDP同理。但是可以TCP8090端口,一個UDP8090端口)
  • ICMP:比如Ping,判斷對方是否在線
  • 原始套接字:可以直接從應用到IP。(傳輸層會檢查IP,若使用此種方式,可以僞造IP)
  • 以瀏覽器發送消息爲例:
  1. 應用層POST /xxx.html HTTP/1.1\r\n\r\nHello World
  2. 傳輸層在前面加tcp端口、源端口、目的端口
  3. 網絡層在前面加源IP、目的IP
  4. 鏈路層在前後加MAC.地址
  5. 然後層層拆解。不匹配就扔。

1.2另一套標準OSI

在這裏插入圖片描述

  • OSI只是理論標準

2.Wireshark抓包工具

  • 下面我們來通過抓包工具抓取數據包,然後進行分析
  • 這個工具是在發送接收網絡數據,收不到或者發不出時,來判斷原因的。
  • 原理是利用原始套接字,穿到操作系統最核心的部分,把所有數據拷貝了一份。
  • 以Windows系統爲例(Linux和Mac版安裝較複雜)

2.1安裝

Wireshark抓包工具

  1. 下載工具,一路Next
  2. 記得勾選(這個工具只是作展示用,而勾選才可以抓包)

2.2抓包

  • 點開一個
  • 紅框裏點擊一個,藍框裏會出現詳細信息
  • 黃框裏是數據在內存中的表現(看不懂是因爲加密了)
    在這裏插入圖片描述
Source Destination Protocal Length info
源IP 目的IP 協議 數據長度 簡單信息描述

藍色的爲Mac地址在這裏插入圖片描述

藍色的爲IP地址在這裏插入圖片描述

藍色的顯示爲UDP方式
在這裏插入圖片描述

藍色爲應用程序的數據在這裏插入圖片描述

2.3過濾

  • 看圖識字吧:
  • 只看TCP在這裏插入圖片描述
  • 只看目的IP爲192.168.1.105在這裏插入圖片描述
  • ip.src可以指定源IP
  • 可以組合條件,用 and 連接(not和or也能用)
  • udp.port指定UDP端口(同理)

3.電腦通信&網絡掩碼

1.1子網掩碼

  • 兩臺電腦通過網線連接,需要設置IP地址和網絡掩碼
  • 子網掩碼可以確定網絡號是誰,主機號是誰。
  • 把IP地址轉換爲2進制。然後執行按位與操作。
  • 按位與操作: 都爲1 ,才爲1,只要有1個0,那麼就是0
  • 比如192爲1100 0000。255爲1111 1111。兩者按位與結果爲:1100 0000。
  • 也就是說,子網掩碼255所在位爲網絡號,0所在位爲主機號。

1.2集線器&交換機

1.2.1集線器Hub

  • 網絡裏傳輸的是數據的信號,而不是電流
  • 集線器發出的數據是以廣播的形式。容易卡,所以被淘汰了。
  • 擴展塢也是一種Hub。

1.2.2交換機switch

  • 該廣播廣播,該單播單播
  • 要發送數據,必須得知道Mac地址
  • ARP協議可以根據IP地址找到Mac地址(Windows的cmd下輸入arp -a可以查看)
  • ARP查找的過程:
  1. 電腦廣播一個數據包
  2. 所有的網卡,默認還接受另一個Mac地址(廣播Mac地址):FF.FF.FF.FF.FF.FF
  3. 對應的IP地址會接包,並單播回送一個數據。然後電腦接收到Mac地址,會根據IP儲存起來。

1.2.3arp攻擊

  1. 假如A要給B發信息。
  2. C主動給A發自己的Mac地址,然後說是B的。給B發自己的Mac地址,然後說是A的。這樣A給B發信息,C可以修改、劫持、監聽

1.3路由器Router

  • 交換機連接多臺電腦進一個網絡。路由器連接多個網絡。
  • 路由器至少有兩個網卡。每個網卡與其對應的交換機處於同一個小網絡內。路由器內的多張網卡可以發生數據通信。
  • 一臺電腦要給另一個網絡的電腦發送消息,需要通過默認網關。默認網關一般就是路由器。
  • 網關: 收到了數據,並把數據發出去的代理,就叫網關。
  • 兩個不同網絡內的電腦在通信過程中IP地址不發生任何變化,Mac地址會發生變化(通過網關代理)(類似在課堂上兩個相距甚遠的情侶傳紙條)
  • Mac地址僅僅是收發雙方的,逐級變化。IP是最終希望通信的兩方之間的,所以不會變。IP是一個邏輯上的地址。
  • 路由器之間有一個路由發現協議。可以知道彼此的MAC、IP地址

1.4複雜通信過程

  1. 解析域名
  1. 發送數據給默認網關
  2. 數據轉手給其它網關,直到到達DNS服務器網絡內
  3. 數據發送給DNS服務器,得到IP地址
  4. 再一路往回傳
  1. 向目標服務器發送tcp的三次握手
  2. 發送HTTP請求數據,等待服務器應答
  3. 發送TCP四次揮手
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章