Python日常筆記(28)-http協議-模仿web服務器

簡單來說就是瀏覽器和服務器之間的一種通信協議.

看一個圖來模擬客戶端和服務端通信的過程

瀏覽器像服務器發送的請求頭信息格式分析

GET /index.html HTTP/1.1
表示客戶端請求的是/index.html地址,HTTP/1.1表示http協議的1.1版本,一般GET與HTTP/1.1之間顯示的就是客戶端訪問的地址

Host: 192.168.153.1:8080
表示請求服務器的地址和端口

Connection: keep-alive
表示爲長連接

Upgrade-Insecure-Requests: 1
由瀏覽器自己規定的

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
表示瀏覽器可以接收什麼樣的格式

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
表示客戶端爲什麼瀏覽器什麼版本

Accept-Encoding: gzip, deflate
表示是什麼壓縮格式

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
表示客戶端能接收的語言

服務器返回給客戶端頭信息分析(以百度爲例)

HTTP/1.1 200 OK
表示請求成功,客戶端請求的頁面有
Bdpagetype: 1
Bdqid: 0x8301fa3c00015b7e

Cache-Control: private
表示緩存是私有的

Connection: keep-alive
Content-Encoding: gzip
表示回覆的壓縮格式gzip

Content-Type: text/html;charset=utf-8
表示返回告訴瀏覽器是一個什麼格式,什麼編碼

Date: Sat, 14 Mar 2020 03:47:21 GMT
服務器當前時間

Expires: Sat, 14 Mar 2020 03:47:19 GMT
Server: BWS/1.1
表示百度服務器的一個簡稱

Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=30971_1430_21124_30826_30997_30824_26350_30717; path=/; domain=.baidu.com
以上三個表示追蹤用戶瀏覽信息的

Strict-Transport-Security: max-age=172800
Traceid: 158415764105892869229440101429550340990
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked

面試題:問題服務器先調用close關閉,馬上重啓會出現端口被佔用的情況。

釋放資源時一般是客戶端先關閉就不會出現這種問題。
那麼原因就是就是Tcp的3次握手和4次揮手,先解釋一下:
3次握手就是雙方準備資源
4次揮手就是將雙方的資源全部釋放(各自的收和發都釋放)

一般來說,由於服務端需要綁定端口,先調用close之後,那麼客戶端就會延遲2-5分鐘在進行調用close。
這時候資源並沒有全部釋放掉,所以馬上重啓服務端就會出現端口被佔用的情況.

練習寫一個服務器

可以直接從瀏覽器中輸入地址即可將瀏覽的數據返回給客戶端瀏覽器

# 導入socket
import socket
# 導入正則表達式
import re


def send_error(tcp_clinet_socket):
   # 否則返回一個沒有找到頁面
   response = "HTTP/1.1 404 NOT FOUND\r\n"
   response += "\r\n"
   response += "沒有找到該頁面"
   tcp_clinet_socket.send(response.encode("gbk")) # 先發送頭部


# 解析數據並返回
def recv_data(tcp_clinet_socket):
   request_data = tcp_clinet_socket.recv(1024).decode("utf-8")
   # 解析數據並返回/index.html,格式GET /index.html HTTP/1.1
   str_lines = request_data.splitlines()
   if len(str_lines) == 0:
       send_error(tcp_clinet_socket) # 發送錯誤數據

   print(str_lines)
   # ret = re.match("[^/]+ (/[\w]+.[\w]+) ", str_lines[0])
   ret = re.match("[^/]+(/[^ ]+)", str_lines[0])
   if ret:
       file_name = ret.group(1) # index.html
       if file_name == "/":
           file_name = "/index.html"

       local_file_name = "." + file_name
       response = "HTTP/1.1 200 OK\r\n"
       response += "\r\n"
       try:
           f = open(local_file_name, "rb")
       except:
           send_error(tcp_clinet_socket) # 發送錯誤數據
       else:
           # 發送數據給客戶端
           tcp_clinet_socket.send(response.encode("utf-8")) # 先發送頭部
           # 在發送body
           return_data = f.read()
           tcp_clinet_socket.send(return_data)
   else:
       send_error(tcp_clinet_socket) # 發送錯誤數據

   tcp_clinet_socket.close()


def main():
   # 1.創建套接字
   tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   # 允許服務端立即使用上次綁定的端口(port)
   tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
   # 2.綁定端口
   tcp_server_socket.bind(("", 9999))
   # 3.監聽客戶端
   tcp_server_socket.listen(128)
   while True:
       # 4.等待客戶端鏈接
       tcp_clinet_socket, addr_socket = tcp_server_socket.accept()
       # 5.接收客戶端數據
       recv_data(tcp_clinet_socket)

   tcp_server_socket.close()

if __name__ == "__main__":
   main()


隨便複製的一個頁面,頁面的文件名最好不要使用中文,要不然會有編碼問題需要解決

作者:阿超
原創公衆號:『Python日常筆記』,專注於 Python爬蟲等技術棧和有益的程序人生,會將一些平時的日常筆記都慢慢整理起來,也期待你的關注和阿超一起學習,公衆號回覆【csdn】優質資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章