網絡編程
一、網絡編程基礎
python 的網絡編程模塊主要支持兩種Internet協議: TCP 和 UDP.
1.1通信協議
通信協議也叫網絡傳輸協議或簡稱為傳送協議(Communications Protocol),是指計算機通信或網絡設備的共同語言。
現在最普及的計算機通信為網絡通信,所以“傳送協議”一般都指計算機通信的傳送協議,如:TCP/IP、NetBEUI、HTTP、FTP等。
然而,傳送協議也存在於計算機的其他形式通信,例如:面向對象編程里面對象之間的通信;操作系統內不同程序之間的消息,都需要有一個傳送協議,以確保傳信雙方能夠溝通無間。
1.2TCP/IP協議
在Internet中TCP/IP協議是使用最為廣泛的通訊協議(互聯網上的一種事實的標准)。TCP/IP是英文Transmission Control Protocol/Internet Protocol的縮寫,意思是“傳輸控制協議/網際協議”
TCP/IP 協議是一個工業標准協議套件,專為跨廣域網(WAN)的大型互聯網絡而設計。
TCP/IP 網絡體系結構模型就是遵循TCP/IP 協議進行通信的一種分層體系,現今,Internet和Intranet所使用的協議一般都為TCP/IP 協議。
在了解該協議之前,我們必須掌握基於該協議的體系結構層次,而TCP/IP體系結構分為四層。
第 1 層 網絡接口層
包括用於協作IP數據在已有網絡介質上傳輸的協議,提供TCP/IP協議的數據結構和實際物理硬件之間的接口。比如地址解析協議(Address Resolution Protocol, ARP )等。
第 2 層 網絡層
對應於OSI模型的網絡層,主要包含了IP、RIP等相關協議,負責數據的打包、尋址及路由。還包括網間控制報文協議(ICMP)來提供網絡診斷信息。
第 3 層 傳輸層
對應於OSI的傳輸層,提供了兩種端到端的通信服務,分別是TCP和UDP協議。
第 4 層 應用層
對應於OSI的應用層、表達層和會話層,提供了網絡與應用之間的對話接口。包含了各種網絡應用層協議,比如Http、FTP等應用協議。

附錄:OSI 七層參考模型

1.3 IP 地址和端口號
1.3.1 IP 地址
互聯網協議地址(英語:Internet Protocol Address,又譯為網際協議地址),縮寫為IP地址(英語:IP Address)
IP 地址是分配給網絡上使用網際協議(英語:Internet Protocol, IP)的設備的數字標簽。常見的IP地址分為IPv4與IPv6兩大類。
IPV4地址
IP地址由32位二進制數組成,為便於使用,常以XXX.XXX.XXX.XXX形式表現,每組XXX代表小於或等於255的10進制數。例如維基媒體的一個IP地址是208.80.152.2。
地址可分為A、B、C、D、E五大類,其中E類屬於特殊保留地址。
IP地址是唯一的。目前IP技術可能使用的IP地址最多可有4,294,967,296個(即232)。驟看可能覺得很難會用盡,但由於早期編碼和分配上的問題,使很多區域的編碼實際上被空出或不能使用。加上互聯網的普及,使大部分家庭都至少有一部電腦,連同公司的電腦,以及連接網絡的各種設備都消耗了大量IPv4地址資源。
隨着互聯網的快速成長,IPv4的42億個地址的分配最終於2011年2月3日用盡[1][2]。相應的科研組織已研究出128位的IPv6,其IP地址數量最高可達3.402823669 × 1038個,屆時每個人家居中的每件電器,每件對象,甚至地球上每一粒沙子都可以擁有自己的IP地址。
在A類、B類、C類IP地址中,如果主機號是全1,那么這個地址為直接廣播地址,它是用來使路由器將一個分組以廣播形式發送給特定網絡上的所有主機。32位全為1的IP地址“255.255.255.255”為受限廣播地址("limited broadcast" destination address),用來將一個分組以廣播方式發送給本網絡中的所有主機,路由器則阻擋該分組通過,將其廣播功能限制在本網內部。
IPV6地址
IPv6地址為128位長但通常寫作8組每組四個十六進制數的形式。例如:
2001:0db8:85a3:08d3:1319:8a2e:0370:7344
是一個合法的IPv6地址。
IPv4地址可以很容易的轉化為IPv6格式。舉例來說,如果IPv4的一個地址為135.75.43.52(十六進制為0x874B2B34),它可以被轉化為0000:0000:0000:0000:0000:0000:874B:2B34或者::874B:2B34。同時,還可以使用混合符號(IPv4-compatible address),則地址可以為::135.75.43.52。
1.3.2端口
在網絡技術中,端口(Port)包括邏輯端口和物理端口兩種類型。
物理端口指的是物理存在的端口,如ADSL Modem、集線器、交換機、路由器上用 於連接其他網絡設備的接口,如RJ-45端口、SC端口等等。
邏輯端口是指邏輯意義上用於區分服務的端口,如TCP/IP協議中的服務端口,端口號的范圍從0到65535,比如用於瀏覽網頁服務的80端口,用於FTP服務的21端口等。由於物理端口和邏輯端口數量較多,為了對端口進行區分,將每個端口進行了編號,這就是端口號。
我們主要研究的是邏輯端口號.我們平時所說的端口號也是指的邏輯端口號
端口是一個軟件結構,被客戶程序或服務程序用來發送和接收數據,一台服務器有 256*256個端口。 端口號范圍: 0 - 65535
0-1023是公認端口號,即已經公認定義或為將要公認定義的軟件保留的
1024-65535是並沒有公共定義的端口號,用戶可以自己定義這些端口的作用。
端口與協議有關:TCP和UDP的端口互不相干
二、TCP編程
什么是 TCP 協議
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接(連接導向)的、可靠的、基於IP的傳輸層協議。
彌補了IP協議的不足,屬於一種較高級的協議,它實現了數據包的有力捆綁,通過排序和重傳來確保數據傳輸的可靠(即數據的准確傳輸以及完整性)。
排序可以保證數據的讀取是按照正確的格式進行,重傳則保證了數據能夠准確傳送到目的地!
使用 TCP 協議通信是, 首先創建 TCP 連接, 主動發起連接的叫客戶端, 被動響應連接的叫服務器
比如:
當我們在瀏覽器中訪問新浪主頁時,我們自己的計算機就是客戶端,瀏覽器會主動向新浪的服務器發起連接。如果一切順利,新浪的服務器接受了我們的連接,一個TCP連接就建立起來的,后面的通信就是發送網頁內容了。
2.1什么是Socket
Socket又稱"套接字",應用程序通常通過"套接字"向網絡發出請求或者應答網絡請求,使主機間或者一台計算機上的進程間可以通訊。
可以把Socket理解成類似插座的東西, 通過Socket就可以發送和接受數據了, 就像插座插上電器之后就可以向外提供電能了.
TCP編程的客戶端和服務器端都是通過Socket來完成的.
其實UDP協議通信也是使用的套接字, 和TCP協議稍有差別. TCP是面向連接的套接字, 而UDP是面向無連接的套接字.
套接字的起源可以追溯到20世紀70年底, 他是加利福尼亞大學的伯克利版本 Unix(也成 BSD Unix) 的一部分. 因此, 有時你可能會聽過將套接字稱為伯克利套接字或 BSD 套接字.
套接字最初是為同一主機上的應用程序鎖創建, 使得主機上一個程序(也叫一個進程)與另一個允許的程序進行通信. 這就是所謂的進程間通信(Inter Process Communication IPC)
Socket families
有兩種類型的套接字: 基於文件的和面向對象的.
基於文件的套接字:AF_UNIX
AF_UNIX是基於文件的套接字.
因為兩個進程允許在同一台計算機上, 所以這些套接字都是基於文件的, 這意味着文件系統支持他們的底層基礎結構. 這是能夠說的通的, 因為文件系統是一個運行在同一主機上的多個進程之間的共享常量.
AF_UNIX在編程的時候並不是太常用.
基於網絡的套接字:AF_INET
AF_INET用於基於網絡的Socket. 還有一個地址家族AF_INET6, 用於IPv6.
其實還有一些其他地址家族, 哪些要么是專業的, 過時的, 很少使用的, 要么是仍未實現的.
在所有的地址家族中, AF_INET是使用最廣泛的.
因為本章重點討論網絡編程, 所以本章剩余的內容中, 都是將使用AF_INET
socket模塊
要創建套接字, 必須使用socket模塊下的socket()函數.
他的一般語法如下:
import socket
socket.socket(socket_family, socket_type, protocal=0)
說明:
1, 其中, socket_family是AF_UNIX 或 AF_INET, Socket_type如果是TCP編程是SOCKET_STREAM, 如果是 UDP 編程則使用SOCKET_DGRAM. protocal通常省略, 默認是0
- 返回值就是
Socket對象.Socket對象提供了一些方法來讓我們操作這些套接字.
2.2 TCP 客戶端編程
客戶端代碼參考下面的代碼:
from socket import *
host = "localhost" # 客戶端准備連接的服務器的地址
port = 10000 # 服務器的端口號
address = (host, port) # 服務器的地址
bufSize = 1024 # 客戶端緩沖區的大小(單位字節)
tcpCliSock = socket(AF_INET, SOCK_STREAM) # 所有的套接字都使用 socket 函數來創建
tcpCliSock.connect(address) # 客戶端去連接服務器
while True:
data = input("> ") # 從鍵盤讀取數據
if not data:
break
# 給服務器發送消息. 由於 send 只能發送字節數據,所以把字符串編碼之后再發送
tcpCliSock.send(data.encode("utf-8"))
data = tcpCliSock.recv(bufSize) # 接受服務器發送來的信息
if not data:
break
# 由於通過網絡傳遞過來的其實是字節數據, 解碼之后再輸出
print(data.decode("utf-8"))
tcpCliSock.close()
2.3 TCP 服務器編程
服務器代碼參考下面的代碼:
from socket import *
host = "localhost" # 服務器要綁定的主機地址
port = 10000 # 服務器要監聽的端口號
address = (host, port)
bufSize = 1024 # 設置服務器的緩沖區大小
tcpSevSock = socket(AF_INET, SOCK_STREAM) # 創建 socket 對象
tcpSevSock.bind(address) # 把 socket 綁定到指定的地址和端口
tcpSevSock.listen() # 開啟服務器監聽器
print("正在等等客戶端連接...")
tcpCliSock, cliAddress = tcpSevSock.accept() # 接受客戶端的連接
print("來自:", cliAddress, "的連接...")
while True:
data = tcpCliSock.recv(bufSize) # 接受客戶端發來的數據
if not data:
break
# 把接收到字節數據解碼
data = data.decode("utf-8")
# 向客戶端發送數據. 先把字符串編碼, 再發送
tcpCliSock.send(("我是...服務器...你的信息是:" + data).encode("utf-8"))
tcpCliSock.close() # 關閉客戶端
tcpSevSock.close() # 關閉服務器
2.4 運行服務器和客戶端
執行 TCP 服務器和客戶端
現在開始運行服務器和客戶端程序, 看看他們是如何工作的.
那么應該先運行客戶端還是服務器呢?
當然是先運行服務器, 讓服務器先處於等等客戶端接入的狀態, 這樣在客戶端申請接入的時候才不會出錯.
其實, 服務器是被動端, 客戶端是主動端.
三、UDP編程
UDP簡介
UDP也叫用戶數據報協議
UDP編程相比TCP編程簡單了很多.
因為UDP不是面向連接的, 而是面向無連接的.
TCP是面向連接的, 客戶端和服務端必須連接之后才能通訊, 就像打電話, 必須先接通才能通話.
UDP是面向無連接的, 一方負責發送數據(客戶端), 只要知道對方(接受數據:服務器) 的地址就可以直接發數據了, 但是能不能達到就沒有辦法保證了.
雖然用UDP傳輸面向無連接, 數據不可靠,但它的優點是和TCP比,速度快,對於不要求可靠到達的數據,就可以使用UDP協議。 比如局域網的視頻同步, 使用 udp 是比較合適的:快, 延遲越小越好
創建UDP的Socket對象
創建方式和TCP的Socket一樣的, 只是需要把socket_tpye的值設置為SOCKET_DGRAM
socket(AF_INET, SOCKET_DGRAM)
3.1 UDP客戶端編程
參考下面的代碼:
from socket import *
host = "localhost" # 對方地址
port = 20000 # 對方端口
address = (host, port)
bufSize = 1024
udpCliSock = socket(AF_INET, SOCK_DGRAM)
while True:
data = input("> ")
if not data:
break
# 把數據發送到指定的 udp 服務器
udpCliSock.sendto(data.encode("utf-8"), address)
udpCliSock.close()
3.2 UDP服務器編程
UDP服務器需要做的事情比較少, 除了等待傳入的連接之外, 幾乎不需要做其他工作.
參考下面的代碼:
from socket import *
host = "localhost" # 服務器要綁定的地址
port = 20000 # 服務器要綁定的端口
address = (host, port)
bufSize = 1024
udpServeSock = socket(AF_INET, SOCK_DGRAM)
udpServeSock.bind(address)
while True:
print("等待有人給我發信息:")
data, cliAddress = udpServeSock.recvfrom(bufSize)
print(cliAddress, "發來的信息是:", data.decode("utf-8"))
udpServeSock.close()
3.3 運行UDP服務器和客戶端
仍然需要先執行服務器再執行客戶端.
四、socket模塊其他屬性和函數
在socket模塊中, 除了目前熟悉的socket.socket()函數之外, 還提供了更多用於網絡應用開發的屬性.
| 屬性 | 描述 |
|---|---|
| AF_UNIX, AF_INET, AF_INET6, AF_NETLINK, AF_TIPC | python 中支持的套接字地址家族 |
| SOCK_STREAM, SOCK_DGRAM | 套接字類型(TCP=流, UDP=數據包) |
| has_ipv6 | 指示是否支持 IPv6的布爾標記 |
| 異常 | 描述 |
|---|---|
| error | 套接字相關錯誤 |
| haserror | 主機和地址相關錯誤 |
| gaierror | 地址相關錯誤 |
| timeout | 超時時間 |
| 函數 | 描述 |
|---|---|
| socket() | 創建套接字對象 |
| getaddrinfor() | 獲取一個五元組序列形式的地址信息 |
| getnameinfo() | 給定一個套接字地址, 返回二元組(主機名, 端口號) |
| getfqdn() | 返回完整域名 |
| gethostname() | 返回當前主機名 |
| gethostbyname() | 將一個主機名, 映射到他的 ip 地址 |
| gethostbyname_ex() | gethostbyname()的擴展版本, 返回主機名, 別名主機集合和 ip 地址列表 |
五、python web 客戶端
TCP和UDP是比較低級的協議, 是底層網絡通訊協議, 是當今因特網中大部分客戶端/服務器協議的核心.
大部分情況我們並不會直接使用TCP, UDP去編程, 而是使用更加高級的協議去編程.
比如 HTTP(超文本傳輸協議), FTP(文件傳輸協議)等.
本節內容主要學習使用 HTTP 去訪問互聯網中的內容.
所以我們先從 HTTP 協議開始講起, 他是目前互聯網上應用最廣泛的通信協議.
5.1 HTTP 協議簡介
5.1.1 什么是 HTTP 協議
HTTP協議是Hyper Text Transfer Protocol(超文本傳輸協議)的縮寫,是用於從萬維網(WWW:World Wide Web )服務器傳輸超文本到本地瀏覽器的傳送協議。
HTTP 是基於 TCP/IP 協議的應用層協議。它不涉及數據包(packet)傳輸,主要規定了客戶端和服務器之間的通信格式,默認使用80端口。
5.1.2 HTTP 協議發展簡史
- 最早版本是1991年發布的0.9版。該版本極其簡單,只有一個命令GET。
- 1996年5月,HTTP/1.0 版本發布,內容大大增加。
任何格式的內容都可以發送。這使得互聯網不僅可以傳輸文字,還能傳輸圖像、視頻、二進制文件。這為互聯網的大發展奠定了基礎。 - 1997年1月,HTTP/1.1 版本發布,只比 1.0 版本晚了半年。它進一步完善了 HTTP 協議,一直用到了20年后的今天,直到現在還是最流行的版本。
5.1.3 HTTP 協議工作原理
HTTP協議工作於客戶端-服務端架構為上, 是一種請求應答式的.
瀏覽器(或其他客戶端)作為HTTP客戶端通過URL向HTTP服務端即WEB服務器發送所有請求。Web服務器根據接收到的請求后,向客戶端發送響應信息。
5.1.4 HTTP協議格式
通信規則規定了客戶端發送給服務器的內容格式,也規定了服務器發送給客戶端的內容格式。
客戶端發送給服務器的格式叫“請求協議”;
服務器發送給客戶端的格式叫“響應協議”。
重點學習這兩個格式。
5.1.5 請求協議格式
請求行 例:GET /images/logo.gif HTTP/1.1,表示從/images目錄下請求logo.gif文件。
請求頭 例:Accept-Language: en(很多請求頭)
空行 必須的,服務通過這個空行來區別出請求頭和請求體
請求體 有時候也叫消息體,是可選的,get請求時無請求體,post請求會有。
瀏覽器向服務器發送請求時必須依據該格式,否則服務器無法識別。http協議中的請求行中可以有8種請求方法,但是目前為止,通用和大家都在用的只有兩種:post請求和get請求。
5.1.6 響應協議格式
狀態行;
響應頭信息;
空行;
響應體(響應正文)。
5.1.7 GET 請求和 POST 請求的區別
注意區別就是請求數據的傳送方式:
1.GET 方法
查詢字符串(名稱/值對)是在 GET 請求的 URL 中發送的:
/test/demo_form.asp?name1=value1&name2=value2
2.POST 方法
請求數據(名稱/值對)是在 POST 請求的 HTTP 消息主體中發送的:
POST /test/demo_form.asp HTTP/1.1
Host: w3schools.com
name1=value1&name2=value2
比較
| 項目 | GET | POST |
|---|---|---|
| 后退按鈕/刷新 | 無害 | 數據會被重新提交(瀏覽器應該告知用戶數據會被重新提交)。 |
| 書簽 | 可收藏為書簽 | 不可收藏為書簽 |
| 緩存 | 能被緩存 | 不能緩存 |
| 編碼類型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded 或 multipart/form-data。為二進制數據使用多重編碼。 |
| 歷史 | 參數保留在瀏覽器歷史中。 | 參數不會保存在瀏覽器歷史中。 |
| 對數據長度的限制 | 是的。當發送數據時,GET 方法向 URL 添加數據;URL 的長度是受限制的(URL 的最大長度是 2048 個字符)。 | 無限制。 |
| 對數據類型的限制 | 只允許 ASCII 字符。 | 沒有限制。也允許二進制數據。 |
| 安全性 | 與 POST 相比,GET 的安全性較差,因為所發送的數據是 URL 的一部分。在發送密碼或其他敏感信息時絕不要使用 GET ! | POST 比 GET 更安全,因為參數不會被保存在瀏覽器歷史或 web 服務器日志中。 |
| 可見性 | 數據在 URL 中對所有人都是可見的。 | 數據不會顯示在 URL 中。 |
5.2 URL
前面我們用瀏覽器使用 HTTP 協議去訪問網絡。 但是有一點大家需要記住, 瀏覽器只是 web 客戶端的一種。
- 可以這么說, 任何一個向 web 服務器發送求來獲得應用程序的都是客戶端。
- 瀏覽器作為一個比較特別的客戶端,主要用於瀏覽網頁內容並同其他 web 站點交互。
- 而一個更普通的客戶端可以完成更多的工作,不僅可以下載數據, 還可以存儲、操作數據, 甚至可以將其傳送到另外一個地方或者傳給另外一個應用。
python 提供的 urllib 模塊, 使用它, 就可以編寫可以下載或或者訪問互聯網上信息的簡單 web 客戶端。
你首先需要做的就是為程序提供一個有效的 web 網址, 這個 web 網站就是一個URL。
我們先了解URL是什么?
5.2.1 URL
URL 是Uniform Resource Locator的縮寫, 中文叫:統一資源定位符。
瀏覽網頁需要 URL, 這個 URL 就表示這個網頁的地址。 這個地址用來在 web 上定位定位一個文檔。
如街道地址一樣, URL 地址也有一些結構。URL 使用如下的這種格式:
prot_sch://net_loc/path;params?query#frag
| URL組件 | 描述 |
|---|---|
| pro_sch | 網絡協議, 如:http, https |
| net_loc | 服務器所在地 |
| path | 使用/分割的路徑 |
| params | 可選參數 |
| ? | 可選, 表示后面是查詢參數 |
| query | 可選, 用連接符(&)分割的一系列鍵值對, 如: user=lisi&pwd=aaa |
| #frag | 可選, 指定文檔內特點錨的部分 |
5.3 urllib包和parse模塊
urllib是一個package, 這個package包含了幾個模塊, 這幾個模塊都是使用url來工作.
urllib.request模塊, 用於打開和讀取urlurllib.error模塊, 包含了urllib.request拋出的一些異常.urllib.parse模塊, 解析urlurllib.robotparser模塊, 解析robots.txt文件
5.3.1 urllib包
在python2中的模塊urllib, urlparse, urllib2, 以及其他內容都整合在了urllib單一包中.
urllib和urllib2的內容整合在了urllib.request模塊中urlparse的內容整合在了urllib.parse模塊中urllib包還包括其他模塊如:response, error, robotparse, 后面再學習.
python支持兩種不同的模塊來處理url, 一個是parse, 另一個是request.
兩種模塊的功能不一樣, 下面會分別介紹兩個模塊.
5.3.2 parse模塊
parse是包urllib下的模塊, 只是用來處理url這個字符串本身, 而不負責使用這個url去聯網獲取資源.
parse主要提供了三個功能: urlparse(), urlunparse(), urljoin()
1. parse.urlparse()
urlparse()用來將url字符串解析成我們前面說的那些組件.
語法:
urlparse(urlstr, defProSch='', allowFrag=True)
**說明:
**
- 參數1就是
url字符串. 返回值一個ParseResult類型的數據 defProSch默認網絡協議.allowFrag表示url中是否允許使用片段.
from urllib.parse import *
o = urlparse('http://www.cwi.nl:80/%7Eguido/Python.html')
print(o)
結果:
ParseResult(scheme='http', netloc='www.cwi.nl:80', path='/%7Eguido/Python.html', params='', query='', fragment='')
2. parse.urlunparse()
是把各個部分組合成url字符串.
from urllib.parse import *
o = urlparse('http://www.cwi.nl:80/%7Eguido/Python.html')
urlstr = urlunparse(o)
print(urlstr)
3. parse.urljoin()
urljoin()實現了url的連接功能.
urljoin(base, new_url, allow_fragments=True)
說明:
取得base的根路徑(不包括路徑中末端的文件), 然后與new_url連接起來.
from urllib.parse import *
newUrl = urljoin('http://www.cwi.nl/abc/Python.html', 'FAQ.html')
print(newUrl)
5.4 request模塊
urllib.request模塊提供了許多函數, 可用於從指定URL加載數據, 同時也可以對字符串進行編碼解碼工作, 以便再URL中以正確的形式顯示出來.
request.urlopen()
urlopen(url, data=None[, timeout])
說明:
- 該函數, 打開指定的url並返回類文件對象, 可使用該對象讀取返回的數據.
- 對所有HTTP請求, 最常用的是
GET請求, 向服務器發送的請求參數應該是url的一部分. 注意使用到的參數應該是已經經過url編碼的(使用parse.urlencode()編碼). - 如果是
post請求, 請求的字符串(包括表單數據)應該放在第二個參數data中.
from urllib.request import *
with urlopen("http://www.yztcedu.com") as r:
print(r)
urlopen()的返回值類文件對象
一旦連接成功, urlopen()會返還一個類文件對象, 就像在目標路徑下打開了一個可讀文件。
| urlopne()類文件對象方法 | 描述 |
|---|---|
| f.read([bytes]) | 從文件中讀取所有或bytes個字節 |
| f.readline() | 從f中讀取一行 |
| f.readlines() | 從f中讀取所有行, 作為列表返回 |
| f.close() | 關閉f的url連接 |
| f.fileno() | 返回f的文件句柄 |
| f.info() | 返回f的mime頭文件 |
| f.geturl() | 返回f的真正url |
from urllib.request import *
with urlopen("http://www.yztcedu.com") as r:
for line in r.readlines():
print(line.decode("utf-8"))
