第十二章 網絡編程
網絡協議概述
通信協議:
協議即規則,就好比汽車上路要遵守交通規則一樣,為了使全世界不同類型的計算機都可以連接起來,所以制定了一套全球通用的通信協議——Internet協議。有了Internet協議,任何私有網絡,只要支持這個協議,就可以接入互聯網。
Internet協議主要的協議和層次關系:
?
IP:
- IP協議是整個TCP/IP協議族的核心
- IP地址就是互聯網上計算機的唯一標識
- 目前的IP地址有兩種表示方式,即IPv4和IPv6
- 在命令行下使用
ipconfig
命令可以查看本機的IP地址
TCP協議與UDP協議的區別
TCP:
TCP(Transmission Control Protocol)協議即傳輸控制協議,是建立在IP協議基礎之上的。TCP協議負責兩臺計算機之間建立可靠連接,保證數據包按順序發送到。它是一種可靠的、一對一的、面向有連接的通信協議。
TCP是可靠的協議,這源于TCP的三次握手:
UDP:
UDP(User Datagram Protocol)協議即用戶數據包協議,它是面向無連接的協議,只要知道對方的IP地址和端口,就可以直接發送數據包,由于是面向無連接的,所以無法保證數據一定會到達接收方。
端口號:
- 區分計算機中運行的應用程序的整數
- 端口號的取值范圍是0到65535,一共65536個,其中80這個端口號分配給了HTTP服務,21這個端口號分配給了FTP服務。3306分配給了MySQL數據庫,1521分配給了Oracle數據庫,1433分配給了SQL Server數據庫。
TCP協議與UDP協議的區別:
TCP協議 | UDP協議 | |
---|---|---|
連接方面 | 面向連接的 | 面向無連接 |
安全方面 | 傳輸消息可靠、不丟失、按順序到達 | 無法保證不丟包 |
傳輸效率方面 | 傳輸效率相對較低 | 傳輸效率高 |
連接對象數量方面 | 只能是點對點、一對一 | 支持一對一、一對多、多對多的交互通信 |
TCP協議:面向連接的、可靠的、不丟失的、按順序到達的,但傳輸效率相對較低,只能實現點對點,一對一的數據傳輸。
UDP協議:面向無連接、無法保證不丟包,但傳輸效率高,可以實現一對一、一對多、多對多的交互通信。
Socket套接字
前面提到IP協議、TCP協議和UDP協議,那么這些協議如何落實到程序中呢?
在Python中有一個類——socket,要想編寫網絡通信程序就一定需要用到socket類。
Socket套接字是用于描述IP地址和端口號的。
Socket模塊可以實現TCP編程,也可以實現UDP編程。
socket模塊中socket類的常用方法:
方法名稱 | 功能描述 |
---|---|
bind((ip,port)) | 綁定IP地址和端口 |
listen(N) | 開始TCP監聽,N表示操作系統掛起的最大連接數量,取值范圍1-5之間,一般設置為5 |
accept() | 被動接收TCP客戶端連接,阻塞式 |
connect((ip,port)) | 主動初始化TCP服務器連接 |
recv(size) | 接收TCP數據,返回值為字符串類型,size表示要接收的最大數據量 |
send(str) | 發送TCP數據,返回值是要發送的字節數量 |
sendall(str) | 完整發送TCP數據,將str中的數據發送到連接的套接字,返回之前嘗試發送所有數據,如果成功為None,失敗拋出異常 |
recvfrom() | 接收UDP數據,返回值為一個元組(data,address),data表示接收的數據,address表示發送數據的套接字地址 |
sendto(data,(ip,port)) | 發送UDP數據,返回值是發送的字節數 |
close() | 關閉套接字 |
TCP服務器端代碼的編寫
由于TCP協議是點對點的、一對一的,需要建立連接才可以進行交互的通信,所以TCP編程分為客戶端編程和TCP服務器端編程。
TCP服務器端編程的步驟:
- 使用socket類創建一個套接字對象
- 使用bind((ip,port))方法綁定IP地址和端口號
- 使用listen()方法開始TCP監聽
- 使用accept()方法等待客戶端的連接
- 使用recv()/send()方法接收/發送數據
- 使用close()關閉套接字
# 服務器端
from socket import socket, AF_INET, SOCK_STREAM# 從socket模塊中導入socket類
# 參數AF_INET用于Internet之間的進程通信
# 參數SOCK_STREAM表示的是用TCP協議編程
# AF_INET、SOCK_STREAM大寫說明這是常量# (1)創建socket對象
server_socket = socket(AF_INET, SOCK_STREAM) # AF_INET用于Internet之間的進程通信,SOCK_STREAM表示的是用TCP協議編程
# (2)綁定IP地址和端口
ip = '127.0.0.1' # 本機的IP地址,等同于 localhost
port = 8888 # 端口號
server_socket.bind((ip, port))# (3)使用listen()開始監聽
server_socket.listen(5)
print('服務器已啟動')# (4)等待客戶端的連接
# 客戶端發送過來一個socket對象,以及地址。這里用的是系列解包賦值
client_socket, client_addr = server_socket.accept() # 返回的是一個元組,使用系列解包賦值# (5)接收來自客戶端的數據
data = client_socket.recv(1024) # recv()接收數據
# 客戶端發送過來時需要編碼,服務器端接收時要解碼
print('客戶端發送過來的數據為:', data.decode('utf-8')) # 解碼# (6) 關閉socket
server_socket.close()
TCP客戶端代碼的編寫
TCP客戶端編程的步驟:
- 使用socket類創建一個套接字對象
- 使用connect((host,port))設置連接的主機IP和主機設置的端口號
- 使用recv() / send()方法接收/發送數據
- 使用close()關閉套接字
這里我們需要新建一個Python項目來代表另一臺電腦:
選擇New Window打開,這樣就會打開兩個PyCharm,每一個PyCharm就相當于一臺電腦。
?
# 客戶端
from socket import socket# (1)創建Socket對象
client_socket = socket()
# (2)IP地址和主機接口,向服務器端發送連接請求
ip = '127.0.0.1' # 要連接的主機的IP地址
port = 8888
client_socket.connect((ip, port))
print('----------與服務器連接建立成功------------')# (3)發送數據
# 客戶端發送時需要編碼
client_socket.send('Welcome to python world'.encode('utf-8'))# (4)關閉socket
client_socket.close()
print('發送完畢')
注意:
服務器端和客戶端都編寫好之后,一定要先啟動服務器端,然后啟動客戶端,服務器端等待客戶端發送連接。
當客戶端和服務器端的連接建立好后,客戶端和服務器端的地位是相等的,誰先發送數據都可以。只不過在上面的例子中是客戶端先發送的數據,服務器端接收數據。
TCP編程客戶端與服務器端啟動運行有先后,先啟動運行服務器端再啟動運行客戶端,連接建立之后雙方誰先發送數據均可。
圖示TCP通信過程:
TCP多次通信服務器端代碼編寫
在上面的例子中客戶端與服務器端只進行了一次數據交互,事實上兩者之間可以進行多次數據交互。
客戶端與服務器端進行多次數據交互可以使用循環來實現。首先兩者之間交互的次數不知道,所以應使用while循環實現。
# 多次通信。服務器端
# from socket import socket, AF_INET, SOCK_STREAM
import socket # 這次使用這種導入方式# 從socket模塊中導入socket類
# 參數AF_INET用于Internet之間的進程通信
# 參數SOCK_STREAM表示的是用TCP協議編程
# AF_INET、SOCK_STREAM大寫說明這是常量# (1)創建socket對象
socket_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET用于Internet之間的進程通信,SOCK_STREAM表示的是用TCP協議編程
# (2)綁定IP地址和端口
socket_obj.bind(('127.0.0.1', 8888))# (3)使用listen()開始監聽
socket_obj.listen(5) # 設置最大的連接數量--->5
print('服務器已啟動')# (4)等待客戶端的TCP連接
# 客戶端發送過來一個socket對象,以及地址(IP地址)。這里用的是系列解包賦值
client_socket, client_addr = socket_obj.accept() # 返回的是一個元組,使用系列解包賦值# 服務器端接收/發送數據時用的都是客戶端的socket對象(client_socket)
# (5)接收來自客戶端的數據
# while循環的四個步驟,info是初始化變量
info = client_socket.recv(1024).decode('utf-8') # recv()接收數據.客戶端發送過來時需要編碼,服務器端接收時要解碼
while info != 'bye': # 條件判斷,中間的是循環體if info != '':print('接收到的數據是:', info)# 準備發送數據data = input('請輸入要發送的數據:')# 服務器端回復客戶端client_socket.send(data.encode('utf-8'))if data == 'bye':breakinfo = client_socket.recv(1024).decode('utf-8') # 改變變量# (6) 關閉socket對象
client_socket.close() # 接收到的客戶端的socket對象需要關閉
socket_obj.close() # 服務器端的socket對象需要關閉
注意:
服務器端接收/發送數據時用的都是客戶端的socket對象(client_socket)。【見上方循環體】
TCP多次通信客戶端代碼編寫
# 多次通信。客戶端
import socket# (1)創建Socket對象
client_socket = socket.socket()
# (2)主機IP地址和主機接口,向服務器端發送連接請求
client_socket.connect(('127.0.0.1', 8888))
print('----------與服務器連接建立成功------------')# (3)客戶端先發送數據(在我們多次通信的例子中,編寫的是服務器端先接收數據)
# while循環的四個步驟
info = ''
while info != 'bye':# 準備發送的數據send_data = input('請客戶端輸入要發送的數據:')client_socket.send(send_data.encode('utf-8'))# 判斷if send_data == 'bye':break # 退出循環# 接收一條數據info = client_socket.recv(1024).decode('utf-8')print('收到服務器的響應數據:', info)# (4)關閉socket
client_socket.close()
print('發送完畢')
UDP的一次雙向通信
UDP協議是面向無連接的,只需要知道對方的IP地址和端口號就可以發送數據包,但是它不保證數據包能夠一定到達。
UDP編程客戶端(發送方)步驟:
- 使用socket類創建一個套接字對象
- 準備發送的數據
- 定義接收方的IP地址和端口號
- 使用sendto()/recvfrom()方法發送/接收數據
- 關閉socket對象
UDP編程服務器端(接收方)步驟:
- 使用socket類創建一個套接字對象
- 使用bind()方法綁定IP地址和端口號
- 使用sendto()/recvfrom()方法發送/接收數據
- 關閉socket對象
UDP編程接收方與發送方啟動運行無先后,但先啟動運行發送方,數據包會丟包,因此一般先啟動接收方。
圖示UDP通信過程:
UDP客戶端(發送端)代碼編寫
# UDP客戶端(發送端)
from socket import socket, AF_INET, SOCK_DGRAM# 從socket模塊中導入socket類
# 參數AF_INET用于Internet之間的進程通信
# 參數SOCK_DGRAM表示的是用UDP協議編程
# AF_INET、SOCK_DGRAM大寫說明這是常量# (1)創建Socket對象
send_socket = socket(AF_INET, SOCK_DGRAM)
# (2)準備發送數據
data = input('請輸入要發送的數據:')
# (3)指定接收方的IP地址和端口
ip_port = ('127.0.0.1', 8888)# (4)發送數據
send_socket.sendto(data.encode('utf-8'), ip_port)# (5)接收來自接收方的回復數據
# recvfrom()返回的是一個元組-->(接收到的數據,從哪里獲得的數據即地址)
recv_data, addr = send_socket.recvfrom(1024)
print('接收到的數據為:', recv_data.decode('utf-8'))# (4)關閉socket
send_socket.close()
print('發送完畢')
UDP服務器端(接收端)代碼編寫
# UDP服務器端(接收方)
from socket import socket, AF_INET, SOCK_DGRAM# 從socket模塊中導入socket類
# 參數AF_INET用于Internet之間的進程通信
# 參數SOCK_DGRAM表示的是用UDP協議編程
# AF_INET、SOCK_DGRAM大寫說明這是常量# (1)創建socket對象
recv_socket = socket(AF_INET, SOCK_DGRAM) # AF_INET用于Internet之間的進程通信,SOCK_DGRAM表示的是用UDP協議編程
# (2)綁定IP地址和端口
recv_socket.bind(('127.0.0.1', 8888))# (3)接收來自發送方的數據
# recvfrom()返回的是一個元組-->(接收到的數據,從哪里獲得的數據即地址)
recv_data, addr = recv_socket.recvfrom(1024)
print('接收到的數據為:', recv_data.decode('utf-8'))# (4)準備回復對方的數據
data = input('請輸入要回復的數據:')# (5)回復
recv_socket.sendto(data.encode('utf-8'), addr)# (6) 關閉socket對象
recv_socket.close()
運行效果
UDP模擬客服咨詢小程序
聊天軟件一般都是使用UDP編程實現的,這里我們使用UDP編程模擬客服咨詢。
兩者之間交互的次數不知道,所以應使用while循環實現。
客服人員(接收方)
# UDP模擬客服咨詢小程序,客服人員(接收方)
from socket import socket, AF_INET, SOCK_DGRAM# 從socket模塊中導入socket類
# 參數AF_INET用于Internet之間的進程通信
# 參數SOCK_DGRAM表示的是用UDP協議編程
# AF_INET、SOCK_DGRAM大寫說明這是常量# (1)創建socket對象
recv_socket = socket(AF_INET, SOCK_DGRAM) # AF_INET用于Internet之間的進程通信,SOCK_DGRAM表示的是用UDP協議編程
# (2)綁定IP地址和端口
recv_socket.bind(('127.0.0.1', 8888))while True:# (3)接收來自發送方的數據# recvfrom()返回的是一個元組-->(接收到的數據,從哪里獲得的數據即地址)recv_data, addr = recv_socket.recvfrom(1024)print('客戶說:', recv_data.decode('utf-8'))if recv_data.decode('utf-8') == 'bye':break# (4)準備回復對方的數據data = input('客服回復:')# (5)回復recv_socket.sendto(data.encode('utf-8'), addr)# (6) 關閉socket對象
recv_socket.close()
客戶(發送方)
# UDP模擬客服咨詢小程序,客戶(發送方)
from socket import socket, AF_INET, SOCK_DGRAM# 從socket模塊中導入socket類
# 參數AF_INET用于Internet之間的進程通信
# 參數SOCK_DGRAM表示的是用UDP協議編程
# AF_INET、SOCK_DGRAM大寫說明這是常量# (1)創建Socket對象
send_socket = socket(AF_INET, SOCK_DGRAM)
while True:# (2)準備發送數據data = input('客戶說:')# (3)指定接收方的IP地址和端口ip_port = ('127.0.0.1', 8888)# (4)發送數據send_socket.sendto(data.encode('utf-8'), ip_port)if data == 'bye':break# (5)接收來自接收方的回復數據# recvfrom()返回的是一個元組-->(接收到的數據,從哪里獲得的數據即地址)recv_data, addr = send_socket.recvfrom(1024)print('客服回復:', recv_data.decode('utf-8'))# (4)關閉socket
send_socket.close()
print('發送完畢')