python之網路程式設計
1.網路通訊
簡單來說,網路是用物理鏈路將各個孤立的工作站或主機相連在一起,組成資料鏈路,從而達到資源共享和通訊的目的。
使用網路的目的,就是為了聯通多方然後進行通訊,即把資料從一方傳遞給另外一方。
前面的學習編寫的程式都是單機的,即不能和其他電腦上的程式進行通訊。為了讓在不同的電腦上執行的軟體,之間能夠互相傳遞資料,就需要藉助網路的功能。
- 使用網路能夠把多方連結在一起,然後可以進行資料傳遞
- 所謂的網路程式設計就是,讓在不同的電腦上的軟體能夠進行資料傳遞,即程序之間的通訊
2.ip地址
生活中的地址指的就是,找到某人或某機關或與其通訊的指定地點。在網路程式設計中,如果一臺主機想和另一臺主機進行溝通和共享資料,首先要做的第一件事情就是要找到對方。在網際網路通訊中,我們使用IP地址來查詢到各個主機。
ip地址:用來在網路中標記一臺電腦,比如192.168.1.1;在本地區域網上是唯一的。
2.1ip地址的分類
每一個IP地址包括兩部分:網路地址和主機地址,網路地址表示其屬於網際網路的哪一個網路,主機地址表示其屬於該網路中的哪一臺主機。IP地址通常由點分十進位制(例如:192.168.1.1)的方式來表示,IP地址要和子網掩碼(用來區分網路位和主機位)配合使用。
- A類地址
- 一個A類IP地址由1位元組的網路地址和3位元組主機地址組成,網路地址的最高位必須是“0”,
- 地址範圍:1.0.0.1-126.255.255.254
- 子網掩碼:255.0.0.0
- 二進位制表示為:00000001 00000000 00000000 00000001 - 01111110 11111111 11111111 11111110
- 可用的A類網路有126個,每個網路能容納1677214個主機
- B類地址
- 一個B類IP地址由2個位元組的網路地址和2個位元組的主機地址組成,網路地址的最高位必須是“10”,
- 地址範圍:128.1.0.1-191.255.255.254
- 子網掩碼:255.255.0.0
- 二進位制表示為:10000000 00000001 00000000 00000001 - 10111111 11111111 11111111 11111110
- 可用的B類網路有16384個,每個網路支援的最大主機數為256的2次方-2=65534臺。
- C類地址
- 一個C類IP地址由3位元組的網路地址和1位元組的主機地址組成,網路地址的最高位必須是“110”
- 範圍:192.0.1.1-223.255.255.254
- 子網掩碼:255.255.255.0
- 二進位制表示為: 11000000 00000000 00000001 00000001 - 11011111 11111111 11111111 11111110
- C類網路可達2097152個,每個網路支援的最大主機數為256-2=254臺
- D類地址
- D類IP地址第一個位元組以“1110”開始,它是一個專門保留的地址,並不指向特定的網路,目前這一類地址被用在多點廣播(Multicast)中。
- E類地址
- 以“1111”開始,為將來使用保留,僅作實驗和開發用。
- 私有地址
- 在這麼多網路IP中,國際規定有一部分IP地址是用於我們的區域網使用,也就是屬於私網IP,不在公網中使用的,它們的範圍是:
- 10.0.0.0~10.255.255.255
- 172.16.0.0~172.31.255.255
- 192.168.0.0~192.168.255.255
- 注意事項:
- 每一個位元組都為0的地址(“0.0.0.0”)對應於當前主機。
- IP地址中的每一個位元組都為1的IP地址(“255.255.255.255”)是當前子網的廣播地址。
- IP地址中凡是以“1111”開頭的E類IP地址都保留用於將來和實驗使用。
- IP地址中不能以十進位制“127”作為開頭,該類地址中數字127.0.0.1到127.255.255.255用於迴路測試,如:127.0.0.1可以代表本機IP地址,用http://127.0.0.1就可以測試本機中配置的Web伺服器。
- 網路ID的第一個8位組也不能全置為“0”,全“0”表示本地網路。
3.網路通訊原理
DHCP:用來分配IP
在網路裡都是通過IP地址查詢的主機
DNS:域名 IP地址
4.網路通訊方式
4.1直接通訊
說明:
- 如果兩臺電腦之間通過網線連線是可以直接通訊的,但是需要提前設定好ip地址以及網路掩碼
- 並且ip地址需要控制在同一網段內,例如 一臺為
192.168.1.1
另一臺為192.168.1.2
則可以進行通訊
4.2使用集線器通訊
說明:
- 當有多臺電腦需要組成一個網時,那麼可以通過集線器(Hub)將其連結在一起
- 一般情況下集線器的介面較少
- 集線器有個缺點,它以廣播的方式進行傳送任何資料,即如果集線器接收到來自A電腦的資料本來是想轉發給B電腦,如果此時它還連線著另外兩臺電腦C、D,那麼它會把這個資料給每個電腦都發送一份,因此會導致網路擁堵
4.3使用交換機通訊
說明:
- 克服了集線器以廣播發送資料的缺點,當需要廣播的時候傳送廣播,當需要單播的時候又能夠以單播的方式進行傳送
- 它已經替代了之前的集線器
- 企業中就是用交換機來完成多臺電腦裝置的連結成網路的
4.4使用路由器連線多個網路
4.5複雜的通訊過程
說明:
- 在瀏覽器中輸入一個網址時,需要將它先解析出ip地址來
- 當得到ip地址之後,瀏覽器以tcp的方式3次握手鍊接伺服器
- 以tcp的方式傳送http協議的請求資料給伺服器
- 伺服器tcp的方式迴應http協議的應答資料給瀏覽器
總結
- MAC地址:在裝置與裝置之間資料通訊時用來標記收發雙方(網絡卡的序列號)
- IP地址:在邏輯上標記一臺電腦,用來指引資料包的收發方向(相當於電腦的序列號)
- 網路掩碼:用來區分ip地址的網路號和主機號
- 預設閘道器:當需要傳送的資料包的目的ip不在本網段內時,就會發送給預設的一臺電腦,稱為閘道器
- 集線器:已過時,用來連線多型電腦,缺點:每次收發資料都進行廣播,網路會變的擁堵
- 交換機:集線器的升級版,有學習功能知道需要傳送給哪臺裝置,根據需要進行單播、廣播
- 路由器:連線多個不同的網段,讓他們之間可以進行收發資料,每次收到資料後,ip不變,但是MAC地址會變化
- DNS:用來解析出IP(類似電話簿)
- http伺服器:提供瀏覽器能夠訪問到的資料
5.埠號和套接字
5.1埠
埠就像一個房子的門,是出入這間房子的必經之路。如果一個程式需要收發網路資料,那麼就需要有這樣的埠
在linux系統中,埠可以有65536(2的16次方)個之多!
既然有這麼多,作業系統為了統一管理,所以進行了編號,這就是埠號
5.2埠號
埠是通過埠號來標記的,埠號只有整數,範圍是從0到65535.埠號不是隨意使用的,而是按照一定的規定進行分配。埠的分類標準有好幾種,我們這裡不做詳細講解,只介紹一下知名埠和動態埠。
5.3知名埠號
知名埠是眾所周知的埠號,範圍從0到1023,一些常用的功能使用的號碼是固定的,好比電話號碼110、10086、10010一樣。一般情況下,如果一個程式需要使用知名埠號需要有root許可權。
5.4動態埠號
動態埠的範圍是從1024到65535
之所以稱為動態埠,是因為它一般不固定分配某種服務,而是動態分配。
動態分配是指當一個系統程式或應用程式需要網路通訊時,它向主機申請一個埠,主機從可用的埠號中分配一個供它使用。
當這個程式關閉時,同時也就釋放了所佔用的埠號。
5.5埠號作用
我們知道,一臺擁有IP地址的主機可以提供許多服務,比如HTTP(全球資訊網服務)、FTP(檔案傳輸)、SMTP(電子郵件)等,這些服務完全可以通過1個IP地址來實現。那麼,主機是怎樣區分不同的網路服務呢?顯然不能只靠IP地址,因為IP地址與網路服務的關係是一對多的關係。實際上是通過“IP地址+埠號”來區分不同的服務的。 需要注意的是,埠並不是一一對應的。比如你的電腦作為客戶機訪問一臺WWW伺服器時,WWW伺服器使用“80”埠與你的電腦通訊,但你的電腦則可能使用“3457”這樣的埠。
6.socket簡介
1.不同電腦上的程序之間如何通訊
首要解決的問題是如何唯一標識一個程序,否則通訊無從談起! 在1臺電腦上可以通過程序號(PID)來唯一標識一個程序,但是在網路中這是行不通的。 其實TCP/IP協議族已經幫我們解決了這個問題,網路層的“ip地址”可以唯一標識網路中的主機,而傳輸層的“協議+埠”可以唯一標識主機中的應用程序(程序)。 這樣利用ip地址,協議,埠就可以標識網路的程序了,網路中的程序通訊就可以利用這個標誌與其它程序進行互動。
注意:
- 所謂程序指的是:執行的程式以及執行時用到的資源這個整體稱之為程序(在講解多工程式設計時進行詳細講解)
- 所謂程序間通訊指的是:執行的程式之間的資料共享
6.1什麼是socket
socket(簡稱套接字) 是程序間通訊的一種方式,它與其他程序間通訊的一個主要不同是:
它能實現不同主機間的程序間通訊,我們網路上各種各樣的服務大多都是基於 Socket 來完成通訊的
例如我們每天瀏覽網頁、QQ 聊天、收發 email 等等。
6.2建立socket
在 Python 中 使用socket 模組的函式 socket 就可以完成:
import socket
socket.socket(AddressFamily, Type)
說明:
函式 socket.socket 建立一個 socket,該函式帶有兩個引數:
- Address Family:可以選擇 AF_INET(用於 Internet 程序間通訊) 或者 AF_UNIX(用於同一臺機器程序間通訊),實際工作中常用AF_INET
- Type:套接字型別,可以是 SOCK_STREAM(流式套接字,主要用於 TCP 協議)或者 SOCK_DGRAM(資料報套接字,主要用於 UDP 協議)
建立一個tcp socket(tcp套接字)
import socket # 建立tcp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # ...這裡是使用套接字的功能(省略)... # 不用的時候,關閉套接字 s.close()
建立一個udp socket(udp套接字)
import socket # 建立udp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # ...這裡是使用套接字的功能(省略)... # 不用的時候,關閉套接字 s.close()
說明:
套接字使用流程與檔案的使用流程很類似
- 建立套接字
- 使用套接字收/發資料
- 關閉套接字
7.udp網路程式
UDP 是User Datagram Protocol的簡稱, 中文名是使用者資料報協議。在通訊開始之前,不需要建立相關的連結,只需要傳送資料即可,類似於生活中,"寫信"。
不同電腦之間的通訊需要使用socket,socket可以在不同的電腦間通訊;還可以在同一個電腦的不同程式之間通訊。
7.1Udp傳送資料
建立一個基於udp的網路程式流程很簡單,具體步驟如下:
- 建立客戶端套接字
- 傳送/接收資料
- 關閉套接字
import socket # 1. 建立socket,並連線 # AF_INET:表示這個socket是用來進行網路連線 # SOCK_DGRAM:表示連線是一個 udp 連線 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 傳送資料 # data:要傳送的資料,它是二進位制的資料 # address:傳送給誰,引數是一個元組,元組裡有兩個元素 # 第0個表示目標的ip地址,第1個表示程式的埠號 # 給 192.168.0.101 這臺主機的 9000 埠上傳送了 下午好 # 埠號:0~65536 0~1024 不要用,系統一些重要的服務在使用 # 找一個空閒的埠號 s.sendto('下午好'.encode('utf8'), ('192.168.0.101', 9090)) # 3. 關閉socket s.close()
7.2Udp傳送資料
import socket # 建立一個基於 udp 的網路socket連線 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 繫結埠號和ip地址 s.bind(('192.168.0.101', 9090)) # recvfrom 接收資料 # content = s.recvfrom(1024) # print(content) # 接收到的資料是一個元組,元組裡有兩個元素 # 第 0 個元素是接收到的資料,第 1 個元素是傳送方的 ip地址和埠號 data, addr = s.recvfrom(1024) # recvfrom是一個阻塞的方法,等待 print('從{}地址{}埠號接收到了訊息,內容是:{}'.format(addr[0], addr[1], data.decode('utf8'))) s.close()
8.TCP協議
TCP協議,傳輸控制協議(英語:Transmission Control Protocol,縮寫為 TCP)是一種面向連線的、可靠的、基於位元組流的傳輸層通訊協議,由IETF的RFC 793定義。
TCP通訊需要經過建立連線、資料傳送、終止連線三個步驟。
TCP通訊模型中,在通訊開始之前,一定要先建立相關的連結,才能傳送資料,類似於生活中,"打電話"。
8.1TCP特點
- 面向連線
- 通訊雙方必須先建立連線才能進行資料的傳輸,雙方都必須為該連線分配必要的系統核心資源,以管理連線的狀態和連線上的傳輸。
- 雙方間的資料傳輸都可以通過這一個連線進行。
- 完成資料交換後,雙方必須斷開此連線,以釋放系統資源。
- 這種連線是一對一的,因此TCP不適用於廣播的應用程式,基於廣播的應用程式請使用UDP協議。
- 可靠傳輸
- 1)TCP採用傳送應答機制
TCP傳送的每個報文段都必須得到接收方的應答才認為這個TCP報文段傳輸成功
-
- 2)超時重傳
傳送端發出一個報文段之後就啟動定時器,如果在定時時間內沒有收到應答就重新發送這個報文段。
TCP為了保證不發生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的確認(ACK);如果傳送端實體在合理的往返時延(RTT)內未收到確認,那麼對應的資料包就被假設為已丟失將會被進行重傳。
-
- 3)錯誤校驗
TCP用一個校驗和函式來檢驗資料是否有錯誤;在傳送和接收時都要計算校驗和。
-
- 4) 流量控制和阻塞管理
流量控制用來避免主機發送得過快而使接收方來不及完全收下。
- TCP與UDP的區別
- 面向連線(確認有建立三方交握,連線已建立才作傳輸。)
- 有序資料傳輸
- 重發丟失的資料包
- 捨棄重複的資料包
- 無差錯的資料傳輸
- 阻塞/流量控制
8.2TCP通訊模型
TCP通訊模型中,在通訊開始之前,一定要先建立相關的連結,才能傳送資料。
8.3伺服器和客戶端
伺服器,是提供計算服務的裝置。由於伺服器需要響應服務請求,並進行處理,因此一般來說伺服器應具備承擔服務並且保障服務的能力。 客戶端(Client),與伺服器相對應,為客戶提供本地服務的程式。 客戶端伺服器架構又被稱為主從式架構,簡稱C/S結構,是一種網路架構,它把客戶端與伺服器分開來,一個客戶端軟體的例項都可以向一個伺服器或應用程式伺服器發出請求。
8.3.1TCP客戶端
相比較於TCP服務端,tcp的客戶端要簡單很多,如果說伺服器端是需要自己買手機、查手機卡、設定鈴聲、等待別人打電話流程的話,那麼客戶端就只需要找一個電話亭,拿起電話撥打即可,流程要少很多。
import socket # 基於tcp協議的socket連線 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 在傳送資料之前,必須要先和伺服器建立連線 s.connect(('192.168.31.199', 9090)) # 呼叫connect 方法連線到伺服器 s.send('hello'.encode('utf8')) # udp 直接使用sendto傳送資料 # s.sendto('hello'.encode('utf8'),('192.168.31.199',9090)) s.close()
8.3.2TCP服務端
在程式中,如果想要完成一個tcp伺服器的功能,需要的流程如下:
- socket建立一個套接字
- bind繫結ip和port
- listen使套接字變為可以被動連結
- accept等待客戶端的連結
- recv/send接收發送資料
import socket # 建立一個socket連線 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('192.168.31.199', 9090)) s.listen(128) # 把socket變成一個被動監聽的socket,backlog指定最多允許多少個客戶連線到伺服器。它的值至少為1。收到連線請求後,這些請求需要排隊,如果佇列滿,就拒絕請求。 # ( # <socket.socket fd=512, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.31.199', 9090), raddr=('192.168.31.185', 38096)>, # ('192.168.31.185', 38096) # ) # 接收到的結果是一個元組,元組裡有兩個元素 # 第 0 個元素是客戶端的socket連線,第 1 個元素是客戶端的 ip 地址和埠號 # x = s.accept() # 接收客戶端的請求 client_socket, client_addr = s.accept() # udp裡接收資料,使用的recvfrom data = client_socket.recv(1024) # tcp裡使用recv獲取資料 print('接收到了{}客戶端{}埠號傳送的資料,內容是:{}'.format(client_addr[0], client_addr[1], data.decode('utf8')))
8.4TCP注意事項
- tcp伺服器一般情況下都需要繫結,否則客戶端找不到這個伺服器
- tcp客戶端一般不繫結,因為是主動連結伺服器,所以只要確定好伺服器的ip、port等資訊就好,本地客戶端可以隨機
- tcp伺服器中通過listen可以將socket創建出來的主動套接字變為被動的,這是做tcp伺服器時必須要做的
- 當客戶端需要連結伺服器時,就需要使用connect進行連結,udp是不需要連結的而是直接傳送,但是tcp必須先連結,只有連結成功才能通訊
- 當一個tcp客戶端連線伺服器時,伺服器端會有1個新的套接字,這個套接字用來標記這個客戶端,單獨為這個客戶端服務
- listen後的套接字是被動套接字,用來接收新的客戶端的連結請求的,而accept返回的新套接字是標記這個新客戶端的
- 關閉listen後的套接字意味著被動套接字關閉了,會導致新的客戶端不能夠連結伺服器,但是之前已經連結成功的客戶端正常通訊。
- 關閉accept返回的套接字意味著這個客戶端已經服務完畢
- 當客戶端的套接字呼叫close後,伺服器端會recv解堵塞,並且返回的長度為0,因此伺服器可以通過返回資料的長度來區別客戶端是否已經下線
9.檔案下載案例
TCP伺服器端:
import socket, os server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('192.168.31.199', 9090)) server_socket.listen(128) # 接收客戶端的請求 client_socket, client_addr = server_socket.accept() file_name = client_socket.recv(1024).decode('utf8') # print('接收到了來自{}地址{}埠的資料,內容是:{}'.format(client_addr[0], client_addr[1], data)) if os.path.isfile(file_name): # print('讀取檔案,返回給客戶端') with open(file_name, 'rb') as file: content = file.read() client_socket.send(content) else: print('檔案不存在')
TCP客戶端:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('192.168.31.199', 9090)) # s.send('hello'.encode('utf8')) file_name = input('清輸入您要下載的檔名:') s.send(file_name.encode('utf8')) with open(file_name, 'wb') as file: while True: content = s.recv(1024) if not content: break file.write(content) s.close()