CS模式:客戶端-服務端模式
TCP客戶端開發流程介紹(五步)(C端)
1.創建客戶端套接字對象
2.和服務端套接字建立連接
3.發送數據
4.接收數據
5.關閉客戶端套接字
TCP服務端開發流程(七步)(S端)
1.創建服務端端套接字對象
2.綁定端口號
3.設置監聽
4.等待接受客戶端的連接請求
5.接收數據
6.發送數據
7.關閉套接字
TCP客戶端程序開發
import socket# 第一步:創建客戶端套接字對象
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # socket.AF_INET表示IPV4,socket.SOCK_STREAM表示TCP協議# 第二步:創建連接
tcp_client_socket.connect(("127.0.0.1", 8000)) # 參數是個元組# 第三步:發送數據到服務器端
tcp_client_socket.send("hello".encode("utf-8")) # 這里將字符串編碼成二進制數據# 第四步:接收服務器端返回的數據
recv_data = tcp_client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字節數,decode解碼
print(f"接收到的數據為:{recv_data}") # 將二進制數據解碼成字符串# 第五步:關閉套接字對象
tcp_client_socket.close()
tip:發送和接受的都要是二進制數據,所以要用encode和decode方法將字符串轉換成二進制數據
- encode():將字符串轉換成二進制數據
- decode():將二進制數據轉換成字符串
關于socket.AF_INET、socket.SOCK_STREAM常量的介紹:
- socket.AF_INET(IPv4)
- 這是 Python 中
socket
模塊里的一個常量,AF_INET
代表 Address Family(地址族)為INET
,用于指定網絡通信使用的地址族是 IPv4 地址族。 - 當創建一個套接字(socket)時,通過指定
AF_INET
,告訴操作系統這個套接字將用于基于 IPv4 協議的網絡通信。
- 這是 Python 中
- socket.SOCK_STREAM(TCP)
- 這是
socket
模塊中的另一個常量,用于指定套接字的類型為流套接字。- 當和
AF_INET
一起使用創建套接字時(如前面代碼示例中的socket.socket(socket.AF_INET, socket.SOCK_STREAM)
),它表示創建的是一個基于 TCP(Transmission Control Protocol)協議的流套接字。TCP 是一種面向連接的、可靠的傳輸協議,SOCK_STREAM
類型的套接字利用 TCP 協議提供的特性,如三次握手建立連接、數據的可靠傳輸(通過確認、重傳等機制)、流量控制和擁塞控制等。 - 這種類型的套接字適用于需要保證數據準確無誤地傳輸的應用場景,比如 HTTP(超文本傳輸協議)用于網頁瀏覽,SMTP(簡單郵件傳輸協議)用于發送電子郵件等。它提供了一個字節流的接口,應用程序可以像讀寫文件一樣通過這個套接字進行數據的發送和接收,而不必擔心數據的丟失或損壞,因為 TCP 協議在底層會處理這些問題。
- 當和
- 這是
TCP服務端程序開發(重點)
開發的七步:
1.創建服務端套接字對象
2.綁定端口號
3.設置監聽
4.等待接受客戶端的連接請求:類似于input() → accept()阻塞
5.接收數據
6.發送數據
7.關閉套接字
服務器如何判斷是哪個客戶端連接:
通過accept()方法返回的套接字對象來區分不同的客戶端
import socket# 1.創建服務端套接字對象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # socket.AF_INET表示IPV4,socket.SOCK_STREAM表示TCP協議# 2.綁定端口號
tcp_server_socket.bind(("127.0.0.1", 8000)) # 如果是本機,可以不寫ip地址# 3.設置監聽
tcp_server_socket.listen(128) # 128表示最大連接數# 4.等待接受客戶端的連接請求
new_socket, ip_port = tcp_server_socket.accept() # 阻塞狀態,等待客戶端連接
# tcp_server_socket對象主要用于接收客戶端連接:綁定端口、設置監聽、接收連接
# new_socket對象主要用于接收和發送數據
print(f"新連接的客戶端地址為:{ip_port}")
print(f"新連接的客戶端socket對象為:{new_socket}")# ================================================
# 5.接收數據
recv_data = new_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字節數,decode解碼
print(f"接收到的數據為:{recv_data}")# 6.發送數據
new_socket.send("信息已收到".encode("utf-8")) # 將字符串編碼成二進制數據# 7.關閉新套接字對象(關閉后不能收發消息)和服務端套接字對象(不能接收新連接)
new_socket.close()
tcp_server_socket.close()
當客戶端發送信息后,接收到的data是一個元組,下面是個栗子,元組有兩個元素,第一個元素是套接字對象,第二個元素是客戶端的地址(也是元組)
(<socket.socket fd=432, family=AddressFamily.AF_INET, ttype=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 60925)>, ('127.0.0.1', 60925)
)
注意事項:
- 明確自己開發的到底是客戶端還是服務端
- 客戶端:connect()、send()、recv()、close()
- 服務端:socket()、bind()、listen()、accept()、recv()、send()、close()
- 兩個對象要分清楚
- tcp_server_socket:主要用于接收客戶端連接
- 內部只有服務器本身的信息,可以綁定端口、設置監聽、接收連接
- new_socket:主要用于接收和發送數據
- 內部既有客戶端又有服務器端信息,可以接收和發送數據
- 只能通過這個新套接字來收發數據
- tcp_server_socket:主要用于接收客戶端連接
服務器端面向對象版本
都是七步,不變
面向對象,先分析有哪些對象,創建類,屬性和方法
# 第一步:創建類
class WebServer:# 第四步:創建初始化方法,初始化套接字對象def __init__(self):# 1.創建套接字對象self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET表示IPV4,SOCK_STREAM表示TCP協議# 2.綁定ip和端口號self.tcp_server_socket.bind(("127.0.0.1", 8000)) # 如果是本機,可以不寫ip地址# 這里的8000端口不會隨著服務器關閉而釋放,需要設置端口復用,端口復用在下一篇筆記# 3.設置監聽self.tcp_server_socket.listen(128) # 128表示最大連接數# 第五步:定義一個start方法,啟動服務器,接收客戶端連接def start(self):while True:# 4.等待接受客戶端的連接請求new_socket, ip_port = self.tcp_server_socket.accept()# 5.接收數據recv_data = new_socket.recv(1024).decode("utf-8")print(f"接收到的數據為:{recv_data}")# 6.發送數據new_socket.send("信息已收到".encode("utf-8"))# 7.關閉套接字(只能接收一次信息)# 不能關閉tcp_server_socket,否則無法繼續接收新連接new_socket.close()# 目前一次只能接收一個客戶端,因為是單進程# 如果希望服務器可以同時和多個客戶端收發消息,需要多進程(多任務編程)# 第二步:實例化對象
ws = WebServer()# 第三步:調用start方法,啟動服務器,接收客戶端連接
ws.start()
端口復用:
在上一次關閉服務器后,端口不會立即釋放,需要設置端口復用,才能繼續使用此端口
import socketclass WebServer:# 3、定義一個__init__方法,初始化套接字對象def __init__(self):self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 設置端口復用(在上一次關閉服務器后,端口不會立即釋放,需要設置端口復用)self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 參數2:SOL_SOCKET表示當前套接字對象,參數3:SO_REUSEADDR表示復用的地址,參數4:True表示開啟端口復用(默認是false,要等待很長時間端口才會自動釋放)self.tcp_server_socket.bind(("127.0.0.1", 8000)) # 如果是本機,可以不寫ip地址self.tcp_server_socket.listen(128) # 128表示最大連接數# 4、定義一個start方法,啟動服務器,接收客戶端連接def start(self):while True:# 等待接受客戶端的連接請求new_socket, ip_port = self.tcp_server_socket.accept()# 調用自身的handle_request()方法,用于接收和發送消息(封裝性)self.handle_request(new_socket, ip_port)# 5、定義一個handle_request方法,用于接收和發送消息def handle_request(self, new_socket, ip_port):# 接收某個客戶端發送過來的消息recv_data = new_socket.recv(1024).decode("utf-8") # 實際工作中一條數據大小在1~1.5k之間print(f"接收到的數據為:{recv_data}")# 發送消息給客戶端new_socket.send("信息已收到".encode("utf-8"))# 關閉套接字new_socket.close()# 定義一個程序的執行入口
if __name__ == "__main__":# 1、實例化服務器對象server = WebServer()# 2、啟動服務器server.start()
開發注意事項
1.當TCP客戶端程序想要和TCP服務端程序進行通信的時候必須要先建立連接
2.TCP客戶端程序一般不需要綁定端口號,因為客戶端是主動發起建立連接的。
3.TCP服務端程序必須綁定端口號,否則客戶端找不到這個TCP服務端程序。
4.listen后的套接字是被動套接字,只負責接收新的客戶端的連接請求,不能收發消息。
5.當TCP客戶端程序和TCP服務端程序連接成功后,TCP服務器端程序會產生一個新的套接字,收發客戶端消息使用該套接字。
6.關閉accept返回的套接字意味著和這個客戶端已經通信完畢。
7.當客戶端的套接字調用close后,服務器端的recv會解阻塞,返回的數據長度為O,服務端可以通過返回數據的長度來判斷客戶端是否
已經下線,反之服務端關閉套接字,客戶端的recv也會解阻塞,返回的數據長度也為0。
UDP客戶端
# 導入socket模塊
import socket# 創建UDP套接字對象
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 連接服務器,發送數據
udp_socket.sendto("消息".encode("utf-8"), ("127.0.0.1", 8000))# 關閉套接字
udp_socket.close()