本篇主要爲爲了實現WEB服務器,其中包含了HTTP協議的理解,以及TCP的三次握手、四次揮手等方面相關知識,同時還包含了關於web瀏覽器與服務器之間的通訊過程。html
一般在咱們上網時會在瀏覽器的地址欄輸入網址,①、瀏覽器首先要對URL進行解析,②、隨後經過HTTP協議定義消息內容和步驟,即規定發送請求的格式;③、根據服務器的域名經過操做系統下的解析器(DNS客戶端)向最近的DNS服務器發送請求獲取目標服務器的IP地址並存儲在指定的內存空間內,,經過操做系統下的協議棧以及socket庫將消息發送出去,④、當服務器接收到請求消息會返回響應消息(該響應消息也是根據HTTP協議定義消息內容的格式)最後通過相似的過程返回給web瀏覽器。接下來咱們根據這幾個步驟進行解析:web
1.瀏覽器怎麼對URL進行解析?正則表達式
一般經常使用的訪問數據的機制有如下幾種:chrome
HTTP協議:即 Hypertxt Transfer Protocal 超文本傳送協議)訪問Web服務器,例如:http://www.baidu.com/dir/file1.html瀏覽器
FTP協議:File Transfer Protocol,文件傳輸協議,主要用於文件的上傳和下載,例如:ftp://ftp.glasscom.com/dir/file1.html緩存
File協議:本地文件傳輸協議,例如:file://localhost/c:path/file1.zip。服務器
maito協議:該協議能夠建立一個指向電子郵件地址的超級連接,經過該連接能夠在Internet中發送電子郵件。例如:maito.tone@glasscom.comsession
等等架構
二、根據HTTP協議生成怎樣格式的請求消息和接收的響應消息?app
須要咱們須要知道的是:
HTTP協議:
咱們知道服務端和客戶端之間進行通訊過程即是:首先客戶端根據HTTP協議給服務端發送請求消息,隨後服務器給客戶端發送響應消息。那麼請求消息和響應消息具體是什麼樣的呢?
請求消息:
1 # 如下即是瀏覽器給服務器發送的請求消息 2 3 GET / HTTP/1.1 4 Host: www.baidu.com 5 Connection: keep-alive 6 Cache-Control: max-age=0 7 Upgrade-Insecure-Requests: 1 8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 9 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 10 Accept-Encoding: gzip, deflate, br 11 Accept-Language: zh-CN,zh;q=0.9 12 13 #接下來對這些信息進行解析:
第一部分:請求頭行,包含請求類型、URI、HTTP協議版本;
請求信息類型一般有:get、post、put等等;
第二部分:即緊跟第一行以後的,請求頭部,包含服務器所使用的說明信息;接下來解釋一下這些說明信息的意思:
一、host:請求web服務器的域名地址
二、Connection: 表示是否持久鏈接;即keep-alive表示持久鏈接;
三、Cache-Control:指定請求和響應的緩存機制;no-cache(不能緩存)、no-store(在請求消息中發送將使得請求和響應消息都不使用緩存)、
max-age(客戶機能夠接收生存期不大於指定時間(以秒爲單位)的響應)、max-stale(客戶機能夠接收超出超時期間的響應消息)、min-fresh(客戶機能夠接收響應時間小於當前時間加上指定時間的響應)、
only-if-cached等等
四、User-Agent: HTTP協議運行的瀏覽器類型的詳細信息;好比:谷歌/67.0.3396.99
五、Accept: 指瀏覽器能夠接收的內容類型;
六、Accept-Encoding: 客戶端瀏覽器能夠支持的web服務器返回內容壓縮編碼類型;
七、Accept-Language:瀏覽器支持的語言類型,
八、Cookie: 某些網站爲了辨別用戶身份、進行 session 跟蹤而儲存在用戶本地終端上的數據(一般通過加密);例如當咱們上網時,某些網站能準確的推送咱們想要的信息。
第三部分:"\r\n" --> 分割header和body部分的分界線
響應消息:
1 # 響應消息 2 3 HTTP/1.1 200 OK 4 Bdpagetype: 1 5 Bdqid: 0x8bda58760001baca 6 Cache-Control: private 7 Connection: Keep-Alive 8 Content-Encoding: gzip 9 Content-Type: text/html 10 Cxy_all: baidu+10412ee70bbb9e9eec33f3dbcb3e2df7 11 Date: Wed, 18 Jul 2018 03:26:13 GMT 12 Expires: Wed, 18 Jul 2018 03:25:42 GMT 13 Server: BWS/1.1 14 Set-Cookie: BDSVRTM=0; path=/ 15 Set-Cookie: BD_HOME=0; path=/ 16 Set-Cookie: H_PS_PSSID=1435_21118_20929; path=/; domain=.baidu.com 17 Strict-Transport-Security: max-age=172800 18 Vary: Accept-Encoding 19 X-Ua-Compatible: IE=Edge,chrome=1 20 Transfer-Encoding: chunked 21 22 # 響應消息的解析:
第一部分:響應頭,包含:HTTP協議版本、狀態碼(1XX-告知請求處理進度和狀況,2XX-成功,3XX-表示須要進一步操做,4XX-客戶端錯誤;5XX-服務器錯誤;)
第二部分:響應頭部,包含服務器發送的附加信息;這裏針對幾個重要的進行解析說明:
https://www.cnblogs.com/mylanguage/p/5689879.html -->有詳細說明。
第三部分:"\r\n" --分割header和body的分割線
第四部分:包含服務器向客戶端發送的數據。
以上就請求消息和響應消息的內容格式,由瀏覽器或者客戶端將信息根據HTTP協議轉換而來。
三、怎麼根據域名獲取服務器的IP地址?
前面便提到了要想實現兩臺終端之間的通訊必須知道對方的IP地址和端口號,可是咱們在瀏覽器地址欄輸入的僅是域名,那麼咱們怎麼得到目標服務器的IP地址呢?
首先web瀏覽器會調用操做系統下的解析器即DNS客戶端,隨後由解析器發送請求給最近的DNS服務器(發送過程與C/S架構模型同樣),若所需域名不在最近的DNS服務器,則由該服務器向域的DNS服務器發送詢查消息,若該域名不在根域DNS服務器上,則讓最近DNS服務器向其下級域發送詢查消息,以此遞歸便能查找到該域名所在域的DNS服務器,最後由請求的DNS服務器發送響應消息到最近的DNS服務器,獲得該域名的IP地址;
同時DNS服務器有一個緩存功能,能夠記住以前查詢過的域名,若是所查詢的域名和相關信息已經在緩存中,那麼久能夠直接返回響應。
首先咱們須要知道在客戶端與服務器基於TCP協議創建聯繫時須要通過三次握手,而在斷開鏈接時需經歷四次揮手的過程,那麼咱們來看一下該過程是怎樣的?
三次握手:
解析:
首先服務器一般是處於監聽的狀態,而客戶端一般是主動創建鏈接的一方,即
四次揮手:
同時咱們須要知道的是:一般服務器不會主動斷開鏈接,而是客戶端主動斷開鏈接。
一、客戶端進程發出鏈接釋放報文,而且中止發送數據。釋放數據報文首部,FIN=1,其序列號爲seq=u(等於前面已經傳送過來的數據的最後一個字節的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即便不攜帶數據,也要消耗一個序號。
直接看示例:
import socket import re def server_client(server_client_socket): """接收來自瀏覽器的數據和發送報文""" # 對接收到的請求消息進行解析 request = server_client_socket.recv(1024).decode("utf-8") # 請求消息頭部格式大概爲 : GET /index.html HTTP/1.1 request_lines = request.splitlines() # 將接收到的信息按行分割,並返回列表 ret = re.match(r"[^/]+(/[^ ]*)",request_lines[0]) # 若正則表達式匹配成功有返回值,則檢測是由爲 "/"或者空字符,則設置默認文件爲/index.html文件 if ret: filename = ret.group(1) if filename == "/" or filename == "": filename = "/index.html" # print(request) respone = "HTTP/1.1 200 OK\r\n" respone += "\r\n" # respone += "<h2>hello world</h2>" # 檢測服務器中是否有該文件,有則發生內容,無則返回錯誤 try: f = open("html"+ filename,"rb") except: respone = "HTTP/1.1 404 Not found file \r\n" respone += "\r\n" respone += "<h1>Not found File</h1>" respone = respone.encode("utf-8") else: file_content = f.read() f.close() respone = respone.encode("utf-8") + file_content # 字節之間的拼接 server_client_socket.send(respone) server_client_socket.close() def main(): # 一、建立套接字對象 server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 二、綁定本地信息 server_socket.bind(("",6969)) # 三、監聽 server_socket.listen(128) while True: # 四、等待客戶端鏈接 server_client_socket,client_addr = server_socket.accept() # 五、服務客戶端 server_client(server_client_socket) server_socket.close() if __name__ == "__main__": main()
這樣就實現了一個靜態的web服務器,能夠接收web瀏覽器的請求,對請求的數據進行解析,經過正則表達式獲得須要瀏覽器請求的頁面的文件名,經過文件的讀取獲得數據,並將內容符合給web瀏覽器。