-
socket(簡稱
套接字
) 是進程間通信的一種方式,它與其他進程間通信的一個主要不同是:它能實現不同主機間的進程間通信。 - 在 Python 中 使用socket 模塊的函數 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 協議 """
- 使用流程
- 創建套接字
- 使用套接字發送//接受數據
- 關閉套接字
UDP鏈接
無連接的
- 通訊流程圖如圖(udp鏈接通訊流程圖)
- 創建udp客戶端(用於發送數據)
from socket import * # 1.創建udp套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 2.準備接收方的ip和端口、元組形式 dest_addr = ('192.168.1.103', 8080) # 3.準備要發送的數據 send_data = 'hello word' # 4.發送到指定的ip地址的指點端口中(應用對應的端口) # 網絡通信中只能傳輸二進制所以進行先encode編碼 udp_socket.sendto(send_data.encode('utf-8'), dest_addr ) # 5.關閉套接字 udp_socket.close()
- 創建udp客戶端(用於發送數據後接受數據)
from socket import * # 1.創建udp套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 2.準備接收方的ip和端口、元組形式 dest_addr = ('192.168.1.103', 8080) # 3.準備要發送的數據 send_data = 'hello word' # 4.發送到指定的ip地址的指點端口中(應用對應的端口) # 網絡通信中只能傳輸二進制所以進行先encode編碼 udp_socket.sendto(send_data.encode('utf-8'), dest_addr ) # 5.等待接收對方發送的數據 # 1024表示本次接收的最大字節數 recv_data = udp_socket.recvfrom(1024) # 6.顯示對方發送的數據(當前位置會阻塞進程直到接收到數據) # 接收到的數據recv_data是一個元組 # 第1個元素是對方發送的數據,對方用什麼編碼自己用什麼解碼 # 第2個元素是對方的ip和端口 print(recv_data[0].decode('utf-8')) print(recv_data[1]) # 7.關閉套接字 udp_socket.close()
- 創建udp服務器端用於接受客戶端發送的數據
""" 一般情況下,在一臺電腦上運行的網絡程序有很多,爲了不與其他的網絡程序佔用同一個端口號,往往在編程中, udp的端口號一般不綁定,每一次運行建立udp鏈接都會使用不同的端口。由於服務器端端口需要固定。 所以服務器端需要綁定固定的端口方便客戶端鏈接 """ from socket import * # 1. 創建套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 2. 設置綁定的ip地址和端口號,ip一般不用寫,表示可以通過訪問的本機的任何一個ip發送數據 local_addr = ('', 7788) # 3. 綁定本地的相關信息,如果一個網絡程序不綁定,則系統會隨機分配 udp_socket.bind(local_addr) # 4. 等待接收對方發送的數據(此處會阻塞進程等待客戶端發送數據) recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字節數 # 5. 顯示接收到的數據 print(recv_data[0].decode('utf-8')) # 6. 關閉套接字 udp_socket.close()
- 小結
- 一個udp網絡程序,可以不綁定,此時操作系統會隨機進行分配一個端口,如果重新運行此程序端口可能會發生變化
- 一個udp網絡程序,也可以綁定信息(ip地址,端口號),如果綁定成功,那麼操作系統用這個端口號來進行區別收到的網絡數據是否是此進程的
TCP鏈接
TCP協議,傳輸控制協議(英語:Transmission Control Protocol,縮寫爲 TCP)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。
TCP通信需要經過創建連接、數據傳送、終止連接三個步驟。
TCP通信模型中,在通信開始之前,一定要先建立相關的鏈接,才能發送數據,類似於生活中,"打電話""
- 特點:
- 面向鏈接
- 可靠傳輸
- TCP和UDP的不同
- 面向連接(確認有創建三方交握,連接已創建才作傳輸。)
- 有序數據傳輸
- 重發丟失的數據包
- 捨棄重複的數據包
- 無差錯的數據傳輸
- 阻塞/流量控制
- 通訊流程圖(如圖:TCP鏈接通訊流程圖)
- 搭建簡單TCP客戶端
from socket import * # 創建socket tcp_client_socket = socket(AF_INET, SOCK_STREAM) # 目的信息 server_ip = '190.168.1.103' server_port = 8000 # 鏈接服務器 tcp_client_socket.connect((server_ip, server_port)) # 需要發送的數據 send_data = 'hello word' # 編碼併發送 tcp_client_socket.send(send_data.encode("utf-8")) # 此處會阻塞進程等待服務器端發送數據 # 接收對方發送過來的數據,最大接收1024個字節 recvData = tcp_client_socket.recv(1024) # 解碼並打印接受數據 print('接收到的數據爲:', recvData.decode('utf-8')) # 關閉套接字 tcp_client_socket.close()
-
搭建簡單的TCP服務器端
from socket import * # 創建socket tcp_server_socket = socket(AF_INET, SOCK_STREAM) # 本地信息 address = ('', 7788) # 綁定 bind綁定ip和port tcp_server_socket.bind(address) # 使用socket創建的套接字默認的屬性是主動的,使用listen將其變爲被動套接字用於接收別人的鏈接 #128 指可以有多少個客戶端可以鏈接 tcp_server_socket.listen(128) # 如果有新的客戶端來鏈接服務器,那麼就產生一個新的套接字專門爲這個客戶端服務 # client_socket用來爲這個客戶端服務(新的用於通訊的TCP鏈接)】、 # clientAddr 客戶端的ip和端口 # tcp_server_socket 就可以省下來專門等待其他新客戶端的鏈接 client_socket, clientAddr = tcp_server_socket.accept() # 接收對方發送過來的數據 recv_data = client_socket.recv(1024) # 接收1024個字節 # 對接受數據解碼並打印 print('接收到的數據爲:', recv_data.decode('utf-8')) # 發送一些數據到客戶端 client_socket.send("thank you !".encode('utf-8')) # 關閉爲這個客戶端服務的套接字,只要關閉了,就意味着爲不能再爲這個客戶端服務了,如果還需要服務,只能再次重新連接 client_socket.close() # 關閉服務器端套接字,服務器端用於接受鏈接的套接字一般不關閉 # tcp_server_socket.close()
-
TCP注意點
- tcp服務器一般情況下都需要綁定,否則客戶端找不到這個服務器
- tcp客戶端一般不綁定,因爲是主動鏈接服務器,所以只要確定好服務器的ip、port等信息就好,本地客戶端可以隨機
- tcp服務器中通過listen可以將socket創建出來的主動套接字變爲被動的,這是做tcp服務器時必須要做的
- 當客戶端需要鏈接服務器時,就需要使用connect進行鏈接,udp是不需要鏈接的而是直接發送,但是tcp必須先鏈接,只有鏈接成功才能通信
- 當一個tcp客戶端連接服務器時,服務器端會有1個新的套接字,這個套接字用來標記這個客戶端,單獨爲這個客戶端服務
- listen後的套接字是被動套接字,用來接收新的客戶端的鏈接請求的,而accept返回的新套接字是標記這個新客戶端的
- 關閉listen後的套接字意味着被動套接字關閉了,會導致新的客戶端不能夠鏈接服務器,但是之前已經鏈接成功的客戶端正常通信。
- 關閉accept返回的套接字意味着這個客戶端已經服務完畢
- 當客戶端的套接字調用close後,服務器端會recv解堵塞,並且返回的長度爲0,因此服務器可以通過返回數據的長度來區別客戶端是否已經下線
-
文件下載器
-
服務器端
from socket import * import sys def get_file_content(file_name): """獲取文件的內容""" try: with open(file_name, "rb") as f: content = f.read() return content except: print("沒有下載的文件:%s" % file_name) def main(): if len(sys.argv) != 2: print("請按照如下方式運行:python3 xxx.py 7890") return else: # 運行方式爲python3 xxx.py 7890 port = int(sys.argv[1]) # 創建socket tcp_server_socket = socket(AF_INET, SOCK_STREAM) # 本地信息 address = ('', port) # 綁定本地信息 tcp_server_socket.bind(address) # 將主動套接字變爲被動套接字 tcp_server_socket.listen(128) while True: # 等待客戶端的鏈接,即爲這個客戶端發送文件 client_socket, clientAddr = tcp_server_socket.accept() # 接收對方發送過來的數據 recv_data = client_socket.recv(1024) # 接收1024個字節 file_name = recv_data.decode("utf-8") print("對方請求下載的文件名爲:%s" % file_name) file_content = get_file_content(file_name) # 發送文件的數據給客戶端 # 因爲獲取打開文件時是以rb方式打開,所以file_content中的數據已經是二進制的格式,因此不需要encode編碼 if file_content: client_socket.send(file_content) # 關閉這個套接字 client_socket.close() # 關閉監聽套接字 tcp_server_socket.close() if __name__ == "__main__": main()
-
客戶端
from socket import * def main(): # 創建socket tcp_client_socket = socket(AF_INET, SOCK_STREAM) # 目的信息 server_ip = input("請輸入服務器ip:") server_port = int(input("請輸入服務器port:")) # 鏈接服務器 tcp_client_socket.connect((server_ip, server_port)) # 輸入需要下載的文件名 file_name = input("請輸入要下載的文件名:") # 發送文件下載請求 tcp_client_socket.send(file_name.encode("utf-8")) # 接收對方發送過來的數據,最大接收1024個字節(1K) recv_data = tcp_client_socket.recv(1024) # print('接收到的數據爲:', recv_data.decode('utf-8')) # 如果接收到數據再創建文件,否則不創建 if recv_data: with open("[接收]"+file_name, "wb") as f: f.write(recv_data) # 關閉套接字 tcp_client_socket.close() if __name__ == "__main__": main()
-
錯誤之處歡迎指出