HTTP
一、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協議
- 如圖操作
- 點擊請求和應答後面的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。
- 客戶端:你準備好了嗎?
客戶端給服務器發送一個數字。
爲了顯示是第一次發送的請求,前面用syn標記
- 服務器:我好了,你呢?
服務器將這個數字+1返回,用ack標記
並返回一個新的值,用syn標記
- 客戶端:愉快地開始玩耍吧
客戶端將這個新的值+1返回,用ack標記
2.2四次揮手
- 四次揮手就是雙方釋放資源
- TCP是全雙工的,必須得收發都關了,纔是真正的關閉。
- 揮手的起點是close。
- 客戶端:我他娘地以後不再跟你講一句話。(客戶端關閉發送)
客戶端不和服務器說話時,服務器的
client_socket.recv()
會堵塞。可是這次客戶端如此決絕,服務器就解堵塞。recv_data = client_socket.recv() if recv_data: """來欣賞妾身的舞姿""" else: """滾吧,渣男""" client_socket.close()
- 服務器:好吧,隨你(服務器確認接收到客戶端的信息,客戶端關閉接收)
- 服務器:我也不理你了,滾吧,渣男(服務器關閉對他的發送)
- 客戶端:fine o( ̄︶ ̄)o(客戶端關閉接收)
a. 客戶端調用close(),客戶端關閉發送
b. 服務器給客戶端迴應自己收到了。
c. 服務器的client_socket.recv()還在堵塞。如果加上一個判斷其的條件語句,當收到空時,也就是客戶端執行了close()操作時,服務器解堵塞。服務器關閉對中國客戶端的發送。
e. 客戶端知道服務器不給自己發數據時,客戶端就關閉了收數據。並且向服務器發送一個確認的包。
- 但問題是: 客戶端怎麼知道服務器收到了?答,返回一個確認包。怎麼確認對方收到了返回的確認包?對方再返回一個確認確認的確認包…這不就是死循環嗎?
- 解決辦法: 規定,誰先調用的最後一次Close,誰就等待一個時間,如果超過這個時間沒收到確認包,就再發一個。於是!接收包的這方(在這裏是C),回覆一個確認包後,要等待兩份的這個時間,記做2MSL,如果對方沒收到確認包,它需要再次接收一次確認包。
- 注意: 誰先發起close,誰就要等待。所以要客戶端先發。不然服務器強制關閉後,會出現某某端口被佔用。(客戶端端口不固定換就完事,服務器是固定端口,所以會出問題)
- 添加這一行代碼,就可以在調用close後,立馬結束進程
3.應用戶需求打開頁面
- 獲取用戶需求的頁面名
- 切割發送的請求
- 用re模塊,獲取頁面名
- 爲了防止頁面名爲空,設定一個主頁index.html
- 打開相應文件
- 代碼:
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是長鏈接
- 爲了獲取一個對象的三個數據可以有兩種方式
- 短鏈接:
- 三次握手,接收第一個數據,四次揮手
- 三次握手,接收第二個數據,四次揮手
- 三次握手,接收第三個數據,四次揮手
- 長鏈接:
- 三次握手,接收第一個數據
- 接收第二個數據
- 接收第三個數據,四次揮手
5.2非阻塞實現長鏈接
- close關閉服務套接字,那不就相當於短鏈接了嗎?
- 註釋掉close,瀏覽器就會一直轉圈圈。(因爲不知道接收完數據了)
- 可以用Content-Length請求頭,並獲取文件體長度,只要長度達到了,瀏覽器就知道接收完數據了。
- 代碼實現:
6.epoll
- epoll是當今Linux中採用的方式。比如gevent底層的實現就是用epoll。
- nginx和apache都是用的epoll。
- 這部分了解怎麼用就行。
- epoll是單進程單線程的。
- 操作系統有內存,應用程序也有內存。兩部分內存相互獨立。
- 單線程非阻塞,是在應用程序內部有一個服務套接字列表。每當for循環(輪詢)到某一個套接字時,會複製這個對象的fd(文件索引),然後給操作系統內存。操作系統就會查看是否有屬於它的數據,沒有就異常或阻塞。因爲存在複製過程,列表越大,性能就會顯著降低。
- epoll可以讓應用程序和操作系統內核共用某一塊內存。並且不遍歷了,以事件通知的方式。
6.2epoll代碼實現
- 導入select,創建一個共享內存,並將監聽套接字寫入
- 返回觸發事件的列表
- 當新客戶端連接時,生成一個新套接字
- 當服務套接字接收到信息時、斷開時
- 最終代碼:
四、網絡通信
1.TCP/IP協議
- tcp-ip協議是一類協議的簡稱:
- 鏈路層→網路層→傳輸層→應用層
- 應用層: 包括應用自己規定的協議,瀏覽器之類的還有HTTP協議
- 傳輸層: 一個應用可以既走TCP又走UDP,並且端口可以一致。(TCP內部不能存在兩個8090端口,UDP同理。但是可以TCP8090端口,一個UDP8090端口)
- ICMP:比如Ping,判斷對方是否在線
- 原始套接字:可以直接從應用到IP。(傳輸層會檢查IP,若使用此種方式,可以僞造IP)
- 以瀏覽器發送消息爲例:
- 應用層POST /xxx.html HTTP/1.1\r\n\r\nHello World
- 傳輸層在前面加tcp端口、源端口、目的端口
- 網絡層在前面加源IP、目的IP
- 鏈路層在前後加MAC.地址
- 然後層層拆解。不匹配就扔。
1.2另一套標準OSI
- OSI只是理論標準
2.Wireshark抓包工具
- 下面我們來通過抓包工具抓取數據包,然後進行分析
- 這個工具是在發送接收網絡數據,收不到或者發不出時,來判斷原因的。
- 原理是利用原始套接字,穿到操作系統最核心的部分,把所有數據拷貝了一份。
- 以Windows系統爲例(Linux和Mac版安裝較複雜)
2.1安裝
- 下載工具,一路Next
- 記得勾選(這個工具只是作展示用,而勾選才可以抓包)
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查找的過程:
- 電腦廣播一個數據包
- 所有的網卡,默認還接受另一個Mac地址(廣播Mac地址):FF.FF.FF.FF.FF.FF
- 對應的IP地址會接包,並單播回送一個數據。然後電腦接收到Mac地址,會根據IP儲存起來。
1.2.3arp攻擊
- 假如A要給B發信息。
- C主動給A發自己的Mac地址,然後說是B的。給B發自己的Mac地址,然後說是A的。這樣A給B發信息,C可以修改、劫持、監聽
1.3路由器Router
- 交換機連接多臺電腦進一個網絡。路由器連接多個網絡。
- 路由器至少有兩個網卡。每個網卡與其對應的交換機處於同一個小網絡內。路由器內的多張網卡可以發生數據通信。
- 一臺電腦要給另一個網絡的電腦發送消息,需要通過默認網關。默認網關一般就是路由器。
- 網關: 收到了數據,並把數據發出去的代理,就叫網關。
- 兩個不同網絡內的電腦在通信過程中IP地址不發生任何變化,Mac地址會發生變化(通過網關代理)(類似在課堂上兩個相距甚遠的情侶傳紙條)
- Mac地址僅僅是收發雙方的,逐級變化。IP是最終希望通信的兩方之間的,所以不會變。IP是一個邏輯上的地址。
- 路由器之間有一個路由發現協議。可以知道彼此的MAC、IP地址
1.4複雜通信過程
- 解析域名
- 發送數據給默認網關
- 數據轉手給其它網關,直到到達DNS服務器網絡內
- 數據發送給DNS服務器,得到IP地址
- 再一路往回傳
- 向目標服務器發送tcp的三次握手
- 發送HTTP請求數據,等待服務器應答
- 發送TCP四次揮手