網址組成(四部分)
協定 http, https(https 是加密的http)
主機 g.cn zhihu.com之類的網址
端口 HTTP 協定預設是 80,是以一般不用填寫
路徑 下面的「/」和「/question/31838184」都是路徑
http://www.zhihu.com/ 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:
其中
1,GET
是請求方法(還有POST等,這就是個标志字元串而已)
2,/
是請求的路徑(這代表根路徑)
3,HTTP/1.1
中,1.1是版本号,通用了20年
具體字元串是'GET
傳回的資料如下
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:
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()