網址組成(四部分)
協議 http, https(https 是加密的http)
主機 g.cn zhihu.com之類的網址
端口 HTTP 協議默認是 80,因此一般不用填寫
路徑 下面的「/」和「/question/31838184」都是路徑
http://www.zhihu.com/question/31838184
電腦通信靠IP地址,IP地址記不住就發明了域名(domain name),然后電腦自動向DNS服務器(domain name server)查詢域名對應的IP地址
比如g.cn這樣的網址,可以通過電腦的ping程序查出對應IP 地址
➜ ping g.cn
PING g.cn (74.125.69.160): 56 data bytes
端口是什么?
一個比喻:
用郵局互相寫信的時候,ip相當於地址(也可以看做郵編,地址是域名)
端口是收信人姓名(因為一個地址比如公司、家只有一個地址,但是卻可能有很多收信人)
端口就是一個標記收信人的數字。
端口是一個16 位的數字,所以范圍是 0-65535(2**16)
——HTTP協議——
一個傳輸協議,協議就是雙方都遵守的規范。
為什么叫超文本傳輸協議呢,因為收發的是文本信息。
1,瀏覽器(客戶端)按照規定的格式發送文本數據(請求)到服務器
2,服務器解析請求,按照規定的格式返回文本數據到瀏覽器
3,瀏覽器解析得到的數據,並做相應處理
請求和返回是一樣的數據格式,分為4部分:
1,請求行或者響應行
2,Header(請求的 Header 中 Host 字段是必須的,其他都是可選)
3,\r\n\r\n(連續兩個換行回車符,用來分隔Header和Body)
4,Body(可選)
請求的格式,注意大小寫(這是一個不包含Body的請求):
原始數據如下
'GET / HTTP/1.1\r\nhost:g.cn\r\n\r\n'
打印出來如下
GET / HTTP/1.1
Host: g.cn
其中
1,GET 是請求方法(還有POST等,這就是個標志字符串而已)
2,/ 是請求的路徑(這代表根路徑)
3,HTTP/1.1 中,1.1是版本號,通用了20年
具體字符串是'GET / HTTP/1.1\r\nhost:g.cn\r\n\r\n'
返回的數據如下
HTTP/1.1 301 Moved Permanently
Alternate-Protocol: 80:quic,p=0,80:quic,p=0
Cache-Control: private, max-age=2592000
Content-Length: 218
Content-Type: text/html; charset=UTF-8
Date: Tue, 07 Jul 2015 02:57:59 GMT
Expires: Tue, 07 Jul 2015 02:57:59 GMT
Location: http://www.google.cn/
Server: gws
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Body部分太長,先不貼了
其中響應行(第一行):
1,HTTP/1.1 是版本
2,301 是「狀態碼」,參見文末鏈接
3,Moved Permanently 是狀態碼的描述
瀏覽器會自己解析Header部分,然后將Body顯示成網頁
——web服務器做什么——
主要就是解析請求,發送相應的數據給客戶端。
例如附件中的代碼(1client.py)就是模擬瀏覽器發送HTTP 請求給服務器並把收到的所有信息打印出來(使用的是最底層的 socket,現階段不必關心這種低層,web開發是上層開發)
// 客戶端代碼實例:
1 # coding: utf-8 2 3 import socket 4 5 6 # socket 是操作系統用來進行網絡通信的底層方案 7 # 簡而言之, 就是發送 / 接收數據 8 9 # 創建一個 socket 對象 10 # 參數 socket.AF_INET 表示是 ipv4 協議 11 # 參數 socket.SOCK_STREAM 表示是 tcp 協議 12 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 # 這兩個其實是默認值, 所以你可以不寫, 如下 14 # s = socket.socket() 15 # s = ssl.wrap_socket(socket.socket()) 16 17 # 主機(域名或者ip)和端口 18 host = 'g.cn' 19 port = 80 20 # 用 connect 函數連接上主機, 參數是一個 tuple 21 s.connect((host, port)) 22 23 # 連接上后, 可以通過這個函數得到本機的 ip 和端口 24 ip, port = s.getsockname() 25 print('本機 ip 和 port {} {}'.format(ip, port)) 26 27 # 構造一個 HTTP 請求 28 http_request = 'GET / HTTP/1.1\r\nhost:{}\r\n\r\n'.format(host) 29 # 發送 HTTP 請求給服務器 30 # send 函數只接受 bytes 作為參數 31 # str.encode 把 str 轉換為 bytes, 編碼是 utf-8 32 request = http_request.encode('utf-8') 33 print('請求', request) 34 s.send(request) 35 36 # 接受服務器的響應數據 37 # 參數是長度, 這里為 1023 字節 38 # 所以這里如果服務器返回的數據中超過 1023 的部分你就得不到了 39 response = s.recv(1023) 40 41 # 輸出響應的數據, bytes 類型 42 print('響應', response) 43 # 轉成 str 再輸出 44 print('響應的 str 格式', response.decode('utf-8'))
// 服務端代碼實例
1 import socket 2 3 4 # 這個程序就是一個套路程序, 套路程序沒必要思考為什么會是這樣 5 # 記住套路, 能用, 就夠了 6 # 運行這個程序后, 瀏覽器打開 localhost:2000 就能訪問了 7 # 8 # 服務器的 host 為空字符串, 表示接受任意 ip 地址的連接 9 # post 是端口, 這里設置為 2000, 隨便選的一個數字 10 host = '' 11 port = 2000 12 13 # s 是一個 socket 實例 14 s = socket.socket() 15 # s.bind 用於綁定 16 # 注意 bind 函數的參數是一個 tuple 17 s.bind((host, port)) 18 19 20 # 用一個無限循環來處理請求 21 while True: 22 # 套路, 先要 s.listen 開始監聽 23 # 注意 參數 5 的含義不必關心 24 s.listen(5) 25 # 當有客戶端過來連接的時候, s.accept 函數就會返回 2 個值 26 # 分別是 連接 和 客戶端 ip 地址 27 connection, address = s.accept() 28 29 # recv 可以接收客戶端發送過來的數據 30 # 參數是要接收的字節數 31 # 返回值是一個 bytes 類型 32 request = connection.recv(1024) 33 34 # bytes 類型調用 decode('utf-8') 來轉成一個字符串(str) 35 print('ip and request, {}\n{}'.format(address, request.decode('utf-8'))) 36 37 # b'' 表示這是一個 bytes 對象 38 response = b'<h1>Hello World!</h1>' 39 # 用 sendall 發送給客戶端 40 connection.sendall(response) 41 # 發送完畢后, 關閉本次連接 42 connection.close()