02_套接字程式設計(socket抽象層)
1.套接字概述
1.套接概述: 套接是進行網路通訊的一種手段(socket)
2.套接字分類:
流式套接字(SOCK_STREAM): 傳輸層基於tcp協議進行通訊
資料報套接字(SOCK_DEGAM): 傳輸層基於udp協議進行通訊
原始套接字(SOCK_RAW): 訪問底層協議的套接字
3.TCP與UDP通訊模型流程圖: https://www.processon.com/view/link/5ef43bfd1e0853263742690b
4.套接字屬性和方法
import socket s = socket.socket() #預設會建立流式套接字 # 功能: 獲取套接字的描述符 # 描述符: 每一個IO作業系統都會分配一個不同的整數與之對應,該整數極為此IO操作的描述符 s.fileno() # 12 print(s.fileon()) # 12 # 獲取套接字型別 s.type # <SocketKind.SOCK_STREAM: 1> print(s.type) # SocketKind.SOCK_STREAM # 獲取套接字繫結的地址 s.getsockname() # ('0.0.0.0', 0) s.bind(("127.0.0.1", 7890)) print(s.getsockname()) #('127.0.0.1', 7890) # 使用accept生成的套接字呼叫,獲取該套接字對應的客戶端的地址,在一個伺服器有多個客戶端連線時常會用到這個方法 s.listen(128) conn, addr = s.accept() # 阻塞時需要找一個使用者連線 conn.getpeername() # ('127.0.0.1', 53519) print(conn.getpeername()) # ('127.0.0.1', 53519) # s.setsockopt(level, optname, value) 設定套接字選項 # 引數 level: 定義的選項型別,常用選項(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET)# 引數 optname: 根據level選項確定的子選項 # 引數 value: 根據子選項設定的值 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 設定埠重用 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # 設定套接字允許接收廣播 # s.getsockopt(level, optname) 獲取套接字選項 # 引數 level: 定義的選項型別,常用選項(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET) # 引數 optname: 根據level選項確定的子選項 # 返回值: 返回根據子選項設定的值 s.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) # 1 # 面向鎖的套接字方法 s.setblocking() # 設定套接字阻塞與非阻塞模式,引數預設為True表示阻塞 s.settimeout() # 設定阻塞套接字操作的超時時間 s.gettimeout() # 獲取阻塞套接字操作的超時時間 # 面向檔案的套接字函式 s.fileno() # 套接字的檔案描述符 s.makefile() # 建立一個與該套接字相關的檔案
2.TCP流式套接字
1.TCP服務端流程
1.建立套接字
sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STERAM, proto=0) # 建立套接字並返回套接字物件
引數:
socket_family: 選擇地址族種類AF_INET(UNIX)
socket_type: 套接字型別 流式(SOCK_STREAM),資料報(SOCK_DGRAM)
proto: 子協議型別,tcp和udp都沒有用到自協議因此預設為0
2.繫結IP和埠號
sockfd.bind(("", 7890)) # 繫結IP和埠號
引數: 型別為元組('127.0.0.1', 7890) # 第一項是字串形式的IP,第二項是埠號
3.讓套接字具有監聽功能
sockfd.listen(n) # 使套接字變為監聽套接字,同時建立監聽佇列
引數: n監聽佇列大小,一般設定為128
4.等待客戶端連線
new_socket, client_addr = socket.accept() # 阻塞等待客戶端連線
返回值: 型別為元祖(new_socket, client_addr)
第一項: 返回一個新的套接字用來和客戶端通訊
第二項: 返回連線的客戶端的地址
5.訊息的收發
接收: new_socket.recv(buffer) # 接收訊息
引數: 一次接收訊息的大小, 即一次收多少位元組
返回值: 接收到的內容, 型別為位元組
傳送: new_socket.send(data) # 傳送訊息,當沒有接收端的時候send操作會導致管道破裂(broken pipe)
引數: 傳送的內容(bytes), 型別為位元組
返回值: 傳送了多少個位元組
6.關閉套接字
new_socket.close() # 關閉為客戶端服務的套接字
sockfd.close() # 關閉監聽套接字
2.TCP客戶端流程
1.建立流式套接字: sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STREAM, proto=0)
2.發起連線請求
sockfd.connect(('127.0.0.1', 7890)) # 發起連線請求
引數(元組): 第一項是伺服器的IP,第二項是伺服器的PORT
3.收發訊息: sockfd.recv() sockfd.send()
4.關閉套接字: sockfd.close()
3.TCP服務端-通訊示例
import socket def main(): # 建立資料流套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 設定埠重用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 繫結埠 tcp_server_socket.bind(("", 7890)) # 讓預設的套接字由主動變為被動監聽 listen tcp_server_socket.listen(128) # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務 while True: print("等待一個新的客戶端的到來...") new_client_socket, client_addr = tcp_server_socket.accept() print("客戶端' %s '已經到來" % str(client_addr)) # 迴圈目的: 為同一個客服端服務多次 while True: # 接收客戶端傳送過來的請求 recv_data = new_client_socket.recv(1024) # recv解堵塞的兩種方式: 1.客戶端傳送過來資料,2.客戶端呼叫了close if recv_data: print("客戶端' %s '傳送過來的請求是: %s" % (str(client_addr), recv_data.decode("utf-8"))) # 回送資料給客戶端表示響應客戶端的請求 send_data = "----ok----Request accepted" new_client_socket.send(send_data.encode("utf-8")) else: break # 關閉accept返回的套接字 new_client_socket.close() print("已經為 %s 客戶端已經服務完畢" % str(client_addr)) # 關閉監聽套接字 tcp_server_socket.close() if __name__ == "__main__": main()
4.TCP客戶端-通訊示例
import socket def main(): # 建立資料流套接字 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 連線伺服器 server_ip = input("請輸入要連線的伺服器的ip:") serve_port = int(input("請輸入要連線的伺服器的port:")) server_addr = (server_ip, serve_port) tcp_client_socket.connect(server_addr) # 連線失敗會報錯 # s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果連線失敗不會報錯,會返回錯誤的編碼 # 傳送資料 send_data = input("請輸入要發生的資料:") tcp_client_socket.send(send_data.encode("utf-8")) # 接收伺服器傳送過來的資料 recv_data = tcp_client_socket.recv(1024) print("接收到的資料為:%s" % recv_data.decode("utf-8")) # 關閉套接字 tcp_client_socket.close() if __name__ == "__main__": main()
5.TCP服務端-檔案下載示例
import socket def send_file_client(nwe_client_socket, client_addr): # 1.接收客戶端傳送過來的需要下載的檔名 file_name = nwe_client_socket.recv(1024).decode("utf-8") print("客戶端 %s 要下載的檔案是:%s" % (str(client_addr), file_name)) # 2.開啟檔案讀取資料 file_content = None try: f = open(file_name, "rb") file_content = f.read() f.close() except Exception as ret: print("沒有要下載的檔案%s" % file_name) # 3.傳送檔案的資料給客戶端 if file_content: nwe_client_socket.send(file_content) def main(): # 建立套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 設定埠重用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 繫結本地資訊 local_addr = ("", 7890) tcp_server_socket.bind(local_addr) # 開啟監聽 tcp_server_socket.listen(128) while True: # 等待使用者連線 nwe_client_socket, client_addr = tcp_server_socket.accept() # 呼叫傳送檔案函式,完成為客戶端服務 send_file_client(nwe_client_socket, client_addr) # 關閉套接字 nwe_client_socket.close() # 關閉監聽套接字 tcp_server_socket.close() if __name__ == "__main__": main()
6.TCP客戶端-檔案下載示例
import socket def main(): # 建立套接字 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 連線伺服器 dest_ip = input("請輸入下載伺服器的ip:") dest_port = int(input("請輸入下載伺服器的port:")) dest_addr = (dest_ip, dest_port) tcp_client_socket.connect(dest_addr) # 獲取要下載的檔名 download_file_name = input("請輸入要下載的檔名:") # 傳送要下載的檔名 tcp_client_socket.send(download_file_name.encode("utf-8")) # 接收檔案資料 recv_data = tcp_client_socket.recv(1024) # 一次接收1k資料 # 開啟檔案寫入資料 if recv_data: with open("[new]" + download_file_name, "wb") as f: f.write(recv_data) # 關閉套接字 tcp_client_socket.close() if __name__ == "__main__": main()
7.TCP粘包現象
1.傳送接收緩衝區
傳送和接收訊息均放到快取區再進行處理
當recv接收訊息一次接收不完的時候下次會繼續接收,當recv阻塞時,如果客戶端斷開則recv立即返回空字串
2.TCP粘包概述:
1.TCP中資料以資料流的方式傳送接收,每次傳送的資料間沒有邊界,在接收時可能造成資料的粘連即為粘包
2.合包機制造成資料混亂: nagle演算法將多次連續傳送且間隔較小的資料進行打包成一個數據傳輸
3.拆包機制造成資料混亂: 在傳送端因為受到資料鏈路層網絡卡的MTU限制,會將大的超過MTU限制的資料進行拆分成多個小的資料包進行傳輸
當傳輸到目標主機的作業系統層時,會將多個小的資料包合併成原本的資料包
3.粘包如何處理方案:
1.每次傳送訊息和結束位置加標誌,每次收到訊息按結束標準切割
2.傳送的訊息新增結構描述,例如加一個包頭,包頭裡記錄 name:大小, password: 大小
3.當連續傳送的時每次傳送有一個短暫延遲sleep(0.1)
4.tcp粘包參考連結: https://www.cnblogs.com/LY-C/p/9120992.html
8.TCP服務端-粘包現象驗證
import socket def main(): # 建立資料流套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 繫結埠 tcp_server_socket.bind(("", 7890)) # 讓預設的套接字由主動變為被動監聽 listen tcp_server_socket.listen(128) # 等待客戶端連線 new_client_socket, client_addr = tcp_server_socket.accept() print("客戶%s端已連線" % str(client_addr)) # 迴圈目的: 為同一個客服端服務多次 while True: # 接收客戶端傳送過來的請求 recv_data = new_client_socket.recv(1024) # recv解堵塞的兩種方式: 1.客戶端傳送過來資料,2.客戶端呼叫了close if not recv_data: break # 客戶端是先遍歷["Coco", "1355656****", "[email protected]", "xx省xx市xx區xxx"]列表,再分了4次傳送使用者資訊的資料 # 但是此時服務端收到的資料卻是連續的: Coco1355656****[email protected]省xx市xx區xxx print("客戶端' %s '傳送過來的請求是: %s" % (str(client_addr), recv_data.decode("utf-8"))) # 回送資料給客戶端表示響應客戶端的請求 send_data = "----ok----Request accepted" new_client_socket.send(send_data.encode("utf-8")) # 關閉accept返回的套接字 new_client_socket.close() print("已經為 %s 客戶端已經服務完畢" % str(client_addr)) # 關閉監聽套接字 tcp_server_socket.close() if __name__ == "__main__": main()
9.TCP客戶端-粘包現象驗證
import socket import time def main(): # 建立資料流套接字 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 連線伺服器 server_addr = ("", 7890) tcp_client_socket.connect(server_addr) # 傳送資料 user_info = ["Coco", "1355656****", "[email protected]", "xx省xx市xx區xxx"] for i in user_info: tcp_client_socket.send(str(i).encode("utf-8")) # 傳送時新增延時可以解決粘包現象 # time.sleep(0.1) # 接收伺服器傳送過來的資料 recv_data = tcp_client_socket.recv(1024) print("接收到的資料為:%s" % recv_data.decode("utf-8")) # 關閉套接字 tcp_client_socket.close() if __name__ == "__main__": main()
10.TCP服務端-大檔案上傳
import socket import json import struct def main(): # 建立資料流套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 設定埠重用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 繫結埠 tcp_server_socket.bind(("", 7890)) # 讓預設的套接字由主動變為被動監聽 listen tcp_server_socket.listen(128) # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務 while True: print("等待一個新的客戶端的到來...") new_client_socket, client_addr = tcp_server_socket.accept() print("客戶端' %s '已經到來" % str(client_addr)) # 接收客戶端傳送過來的請求 b_len_dic = new_client_socket.recv(4) print(b_len_dic) len_dic = struct.unpack("i", b_len_dic)[0] # unpack得到一個元祖,取下標0的位置獲取到int型別的字典長度 recv_data = new_client_socket.recv(len_dic).decode("utf-8") # new_client_socket.send(b"OK") # 向客戶端傳送檔案準備就緒標識,同時也避免接收資料過快產生粘包 recv_dic = json.loads(recv_data) if recv_dic["opt"] == "upload": filename = "副本" + recv_dic["filename"] with open(filename, "ab") as f: while recv_dic["filesize"]: content = new_client_socket.recv(1024) f.write(content) recv_dic["filesize"] -= len(content) elif recv_dic["opt"] == "download": pass # 關閉accept返回的套接字 new_client_socket.close() print("已經為 %s 客戶端已經服務完畢" % str(client_addr)) # 關閉監聽套接字 tcp_server_socket.close() if __name__ == "__main__": main()
11.TCP客戶端-大檔案上傳
import socket import os import json import struct def main(): # 建立資料流套接字 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 連線伺服器 server_ip = input("請輸入要連線的伺服器的ip:") serve_port = int(input("請輸入要連線的伺服器的port:")) server_addr = (server_ip, serve_port) tcp_client_socket.connect(server_addr) # 連線失敗會報錯 # s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果連線失敗不會報錯,會返回錯誤的編碼 menu = {"1": "upload", "2": "download"} for i in menu: print(i, menu[i]) num = input("請輸入功能選項: ") if num == "1": send_dic = {"opt": menu.get(num), "filename": None, "filesize": None} file_path = input("請輸入一個絕對路徑: ") # 要上傳檔案的絕對路徑 filename = os.path.basename(file_path) # 獲取要上傳檔案的檔名 filesize = os.path.getsize(file_path) # 獲取檔案大小 send_dic["filename"] = filename send_dic["filesize"] = filesize send_data = json.dumps(send_dic) len_dic = len(send_data) # 獲取字典長度,是一個int資料型別,可能是30,也可能是120 b_len_dic = struct.pack("i", len_dic) # 加字典長度打包成一個4位的bytes型別 # 將bytes型別的字典長度 + bytes型別的字典內容,一起傳送給伺服器 tcp_client_socket.send(b_len_dic + send_data.encode("utf-8")) # tcp_client_socket.recv(1024) # 1.向伺服器確認是否可以上傳檔案;2.避免與下列程式碼的send過快造成粘包 with open(file_path, "rb") as f: while filesize: content = f.read(1024) tcp_client_socket.send(content) filesize -= len(content) # 傳送資料 tcp_client_socket.send(send_data.encode("utf-8")) elif num == "2": pass # 關閉套接字 tcp_client_socket.close() if __name__ == "__main__": main()
12.TCP服務端-身份加密驗證
import socket import os import hmac def main(): # 建立資料流套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 設定埠重用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 繫結埠 tcp_server_socket.bind(("", 7890)) # 讓預設的套接字由主動變為被動監聽 listen tcp_server_socket.listen(128) # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務 while True: print("等待一個新的客戶端的到來...") new_client_socket, client_addr = tcp_server_socket.accept() print("客戶端' %s '已經到來" % str(client_addr)) sor = b"hmy" # 伺服器加鹽 r_str = os.urandom(16) # 隨機一個16位長度的bytes型別資料 new_client_socket.send(r_str) # md5加密 md5_obj = hmac.new(sor, r_str) result = md5_obj.digest() # 接收客戶端傳送過來的密文與伺服器的密文對比 recv_msg = new_client_socket.recv(1024) if recv_msg == result: new_client_socket.send(b"success") else: new_client_socket.send(b"failed") # 關閉accept返回的套接字 new_client_socket.close() print("已經為 %s 客戶端已經服務完畢" % str(client_addr)) # 關閉監聽套接字 tcp_server_socket.close() if __name__ == "__main__": main()
13.TCP客戶端-身份加密驗證
import socket import hmac def main(): # 建立資料流套接字 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 連線伺服器 server_ip = input("請輸入要連線的伺服器的ip:") serve_port = int(input("請輸入要連線的伺服器的port:")) server_addr = (server_ip, serve_port) tcp_client_socket.connect(server_addr) # 連線失敗會報錯 # s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果連線失敗不會報錯,會返回錯誤的編碼 sor = b"hmy" # 客戶端加鹽 r_str = tcp_client_socket.recv(1024) # md5加密 md5_obj = hmac.new(sor, r_str) result = md5_obj.digest() # 向伺服器傳送驗證密文 tcp_client_socket.send(result) # 接收驗證結果 recv_msg = tcp_client_socket.recv(1024) print(recv_msg) # 關閉套接字 tcp_client_socket.close() if __name__ == "__main__": main()
14.TCP服務端-切換目錄
import socket import os def send_data(new_client_socket, path): """你給我一個目錄,我把目錄發給client""" lis_dir = os.listdir(path) str_dir = '--'.join(lis_dir) new_client_socket.send(str_dir.encode('utf-8')) def main(): # 建立資料流套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 設定埠重用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 繫結埠 tcp_server_socket.bind(("", 7890)) # 讓預設的套接字由主動變為被動監聽 listen tcp_server_socket.listen(128) # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務 while True: print("等待一個新的客戶端的到來...") new_client_socket, client_addr = tcp_server_socket.accept() print("客戶端' %s '已經到來" % str(client_addr)) abs_path = new_client_socket.recv(1024).decode('utf-8') # 獲取使用者輸入的絕對路徑 current_dir = abs_path + '/' # 以下再處理,都要根據當前路徑去處理,無論是返回上一層,還是進入下一層 send_data(new_client_socket, current_dir) # 把使用者輸入的路徑下的所有檔案及資料夾返回給客戶端 while True: # 測試輸入: /Users/tangxuecheng cmd = new_client_socket.recv(1024).decode('utf-8') if cmd == '..': current_dir = current_dir.split('/')[:-2] current_dir = '/'.join(current_dir) + '/' # if 如果當前是根目錄: # 就返回給客戶端告訴說沒有上一層了 send_data(new_client_socket, current_dir) else: filename = cmd.split(' ')[1] # 獲取使用者輸入的檔名字 current_dir += filename + '/' # 將檔名字新增到當前路徑下,組成一個完整的新路徑 if os.path.isdir(current_dir): # 如果客戶輸入的檔名字是一個資料夾 send_data(new_client_socket, current_dir) else: # 如果不是一個資料夾 new_client_socket.send(b'not a folder') # 關閉accept返回的套接字 new_client_socket.close() print("已經為 %s 客戶端已經服務完畢" % str(client_addr)) # 關閉監聽套接字 tcp_server_socket.close() if __name__ == "__main__": main()
15.TCP客戶端-切換目錄
import socket def main(): # 建立資料流套接字 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 連線伺服器 server_ip = input("請輸入要連線的伺服器的ip:") serve_port = int(input("請輸入要連線的伺服器的port:")) server_addr = (server_ip, serve_port) tcp_client_socket.connect(server_addr) # 連線失敗會報錯 # s = tcp_client_socket.connect_ex(server_addr) # 有返回值,如果連線失敗不會報錯,會返回錯誤的編碼 # 測試輸入: /Users/tangxuecheng abs_path = input('請輸入您的根目錄:') tcp_client_socket.send(abs_path.encode('utf-8')) current_dir = tcp_client_socket.recv(1024).decode('utf-8') print(current_dir.split('--')) while True: cmd = input('請輸入>>>') # cd + 資料夾 .. if cmd == '..': tcp_client_socket.send(cmd.encode('utf-8')) current_dir = tcp_client_socket.recv(1024).decode('utf-8') print(current_dir.split('--')) if cmd == 'cd': filename = input('請輸入一個資料夾名:') tcp_client_socket.send((cmd + ' ' + filename).encode('utf-8')) current_dir = tcp_client_socket.recv(1024).decode('utf-8') print(current_dir.split('--')) # 關閉套接字 tcp_client_socket.close() if __name__ == "__main__": main()
16.TCP應用-web靜態伺服器
# html資料夾網盤連結: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg 密碼: hp9o # 下載完命令列解壓後放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./ import socket def client_handle(client_socket): # 接收客戶端傳送過來的請求資料 request = client_socket.recv(2048) request_handles = request.splitlines() for line in request_handles: print(line) try: f = open("./html/index.html", "r") except IOError: # 找不到檔案時返回404錯誤 response = "HTTP/1.1 404 not found\r\n" response += "\r\n" response += "sorry file not found" else: # 找到檔案時讀取檔案內容 response = "HTTP/1.1 200 OK\r\n" response += "\r\n" for line in f: response += line finally: # 向瀏覽器返回資訊 client_socket.send(response.encode()) # 關閉客戶端套接字 client_socket.close() def main(): # 1.建立套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2.設定埠重用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 4.允許區域網內使用者訪問 local_addr = ("0.0.0.0", 7890) tcp_server_socket.bind(local_addr) # 5.監聽 tcp_server_socket.listen(128) # 5.迴圈服務 while True: # 客戶端連線 client_socket, client_addr = tcp_server_socket.accept() # 為客戶端服務 client_handle(client_socket) # 6.關閉監聽套接字 tcp_server_socket.close() if __name__ == "__main__": main()
3.UDP資料報套接字
1.UDP套接字使用流程 1.建立資料報套接字 sockfd = socket(AF_INET,SOCK_DGRAM) 引數: AF_INET表示ipv4,SOCK_DGRAM表示資料報套接字 2.繫結服務端地址: sockfd.bind("127.0.0.1", 7890) 3.收發訊息 接收訊息(recvfrom) data, addr = sockfd.recvfrom(buffersize) # 一次接收一個數據包,如果是資料包一次沒有接收完則會丟失沒有接收的內容 引數: 每次最多接收訊息的大小(位元組) 返回值: data: 接收到的訊息 addr: 訊息傳送者的地址 傳送訊息(sendto) sockfd.sendto(data, addr) # 傳送訊息 引數: data:要傳送的訊息 addr: 傳送給某個主機的地址 返回值: 傳送訊息的位元組數 4.關閉套接字: sockfd.close() 2.UDP應用-傳送訊息 import socket def main(): # 建立一個udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 指定接訊息收方的地址 dest_addr = ("", 7788) while True: # 從鍵盤上獲取資料 send_data = input("請輸入要傳送的資料:") # 如果輸入的資料是exit則退出程式 if send_data.lower() == "exit": break # 使用套接字收發資料 # udp_socket.sendto(b"hello world!", ("169.254.119.158", 8080)) udp_socket.sendto(send_data.encode("utf-8"), dest_addr) # 關閉套接字 udp_socket.close() if __name__ == "__main__": main() 3.UDP應用-接收訊息 import socket def main(): # 1.建立套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2.繫結本地資訊 local_addr = ("", 7788) udp_socket.bind(local_addr) # 3.接收資料 while True: recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大位元組數 # 4.列印接收的資料 # recv_data這個變數中儲存的是一個元祖(接收到的資料, (傳送方的ip, port)) recv_msg = recv_data[0] # 第1個元素是對方傳送的資料,型別為位元組 recv_addr = recv_data[1] # 第2個元素是對方的ip和埠,型別為元祖 print(type(recv_msg), type(recv_addr)) # <class 'bytes'> <class 'tuple'> # 功能擴充套件: 含有敏感詞彙訊息在輸出時進行顏色標識 color_dic = {"使用者名稱": "\033[32m", "密碼": "\033[33m", "驗證": "\033[0;32;40m"} msg = recv_msg.decode("utf-8") for i in color_dic: if i in msg: color = color_dic[i] print("%s%s:%s\033[0m" % (color, str(recv_addr), msg)) break else: print("%s:%s" % (str(recv_addr), msg)) # ('127.0.0.1', 61804):Tom你好嗎? # 5.關閉套接字 udp_socket.close() if __name__ == "__main__": main() 4.UDP應用-實現廣播的設定 1.將廣播接收端套和傳送端的接字屬性設定為允許接收廣播 2.將廣播發送端的地址設定為傳送給區域網所有終端: ("192.168.0.255", 7890) 或 ("", 7890) 3.廣播風暴: 在一個網路中大量傳送廣播會佔用大量頻寬,解決方案是組播 5.UDP應用-廣播接收端 # broadcast_recv.py import socket def main(): # 1.建立套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2.設定套接字允許接收廣播 udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # 3.設定埠重用 udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 4.固定接收端的埠號 local_addr = ("", 7890) udp_socket.bind(local_addr) # 5.接收資料 while True: try: recv_msg, recv_addr = udp_socket.recvfrom(1024) # 1024表示本次接收的最大位元組數 # 6.列印接收的資料 # print(type(recv_msg), type(recv_addr)) # <class 'bytes'> <class 'tuple'> print("從廣播{}獲取訊息: {}".format(str(recv_addr), recv_msg.decode("utf-8"))) # 7.回送訊息 udp_socket.sendto(b"ok", recv_addr) except (KeyboardInterrupt, SyntaxError): # control + c 終止程式或者語法錯誤時捕獲異常 raise except Exception as e: print(e) # 8.關閉套接字 udp_socket.close() if __name__ == "__main__": main() 6.UDP應用-廣播發送端 # broadcast_send.py import socket import time def main(): # 1.建立一個udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2.設定套接字允許接收廣播 udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # 3.設定傳送廣播的地址 # dest_addr = ("192.168.0.255", 7890) # 這種寫法也可以 dest_addr = ("", 7890) # 將訊息傳送給區域網所有終端 while True: time.sleep(1) # 每隔1秒傳送一次廣播 # 使用套接字收發資料 udp_socket.sendto("開始廣播了...".encode("utf-8"), dest_addr) recv_msg, recv_addr = udp_socket.recvfrom(1024) # 1024表示本次接收的最大位元組數 print("從接收端{}獲取訊息: {}".format(str(recv_addr), recv_msg.decode("utf-8"))) # 關閉套接字 udp_socket.close() if __name__ == "__main__": main() 7.函式實現-UDP聊天器 import socket def send_msg(udp_socket): """獲取鍵盤資料,並將其傳送給對方""" # 1. 從鍵盤輸入資料 msg = input("\n請輸入要傳送的資料:") # 2. 輸入對方的ip地址 dest_ip = input("\n請輸入對方的ip地址:") # 3. 輸入對方的port dest_port = int(input("\n請輸入對方的port:")) # 4. 傳送資料 udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port)) def recv_msg(udp_socket): """接收資料並顯示""" # 1. 接收資料 recv_msg = udp_socket.recvfrom(1024) # recv_msg, recv_ip = udp_socket.recvfrom(1024) # 2. 解碼 recv_ip = recv_msg[1] recv_msg = recv_msg[0].decode("utf-8") # 3. 顯示接收到的資料 print(">>>%s:%s" % (str(recv_ip), recv_msg)) def main(): # 1. 建立套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 繫結本地資訊 udp_socket.bind(("", 7788)) while True: # 3. 選擇功能 print("="*30) print("1:傳送訊息") print("2:接收訊息") print("0:退出系統") print("="*30) op_num = input("請輸入要操作的功能序號:") # 4. 根據選擇呼叫相應的函式 if op_num == "1": send_msg(udp_socket) elif op_num == "2": recv_msg(udp_socket) elif op_num == "0": break else: print("輸入有誤,請重新輸入...") # 5.關閉套接字 udp_socket.close() if __name__ == "__main__": main() 8.物件實現-UDP聊天器 import socket class UdpSocket: def __init__(self): # 1. 建立套接字 self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 繫結本地資訊 self.udp_socket.bind(("", 7788)) # 傳送訊息 def send_msg(self): """獲取鍵盤資料,並將其傳送給對方""" # 1. 從鍵盤輸入資料 msg = input("\n請輸入要傳送的資料:") # 2. 輸入對方的ip地址 dest_ip = input("\n請輸入對方的ip地址:") # 3. 輸入對方的port dest_port = int(input("\n請輸入對方的port:")) # 4. 傳送資料 self.udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port)) # 接收訊息 def recv_msg(self): """接收資料並顯示""" # 1. 接收資料 recv_msg = self.udp_socket.recvfrom(1024) # recv_msg, recv_ip = self.udp_socket.recvfrom(1024) # 2. 解碼 recv_ip = recv_msg[1] recv_msg = recv_msg[0].decode("utf-8") # 3. 顯示接收到的資料 print(">>>%s:%s" % (str(recv_ip), recv_msg)) # 功能選擇 def start_menu(self): while True: print("="*30) print("1:傳送訊息") print("2:接收訊息") print("0:退出系統") print("="*30) op_num = input("請輸入要操作的功能序號:") # 4. 根據選擇呼叫相應的函式 if op_num == "1": self.send_msg() elif op_num == "2": self.recv_msg() elif op_num == "0": break else: print("輸入有誤,請重新輸入...") # 關閉套接字 def __del__(self): print("%s物件已回收" % self) self.udp_socket.close() def main(): # 例項化一個udp聊天器物件 udp_chat = UdpSocket() # 啟動聊天器 udp_chat.start_menu() if __name__ == "__main__": main() 9.自定義socket類 import socket class MySocket(socket.socket): """自定義MySocket類繼承自socket模組中的socket""" def __init__(self, encoding="utf-8"): # 呼叫父類中的初始化方法 super().__init__(type=socket.SOCK_DGRAM) # 自定義預設的編碼格式 self.encoding = encoding def my_sendto(self, send_msg, send_addr): return self.sendto(send_msg.encode(self.encoding), send_addr) def my_recvfrom(self, num): recv_msg, recv_addr = self.recvfrom(num) return recv_msg.decode(self.encoding), recv_addr 10.UDP收發資料包 1.UDP不會發生粘包現象 2.UDP收發資料包大小取捨 udp協議本身對一次收發訊息資料大小限制是: 65535 - ip包頭(20) - udp包頭(8) = 65507 在資料鏈路層網絡卡的MTU一般為1500的限制,所以在鏈路層收發資料包大小限制為: 1500 - ip包頭(20) - udp包頭(8) = 1472 3.UDP收發資料包大小結論 接收資料包: recvfrom(num) # num < 65507 傳送資料包: sendto(num) num > 65507 # 報錯 1472 < num < 65507 # 在資料鏈路層拆包傳送,然而udp本身就是不可靠協議 # 所以一旦拆包後,造成的多個小資料包在網路傳輸中,如果丟失任意一個,那麼此次資料傳輸失敗 num < 1472 # 是比較理想的狀態
4.本地套接字
1.Linux下常用檔案辨識(ls -lh 第一個用來辨識檔案型別) d: 資料夾 -: 普通檔案 l: 連結檔案 s: 套接字檔案,是一個虛擬檔案且大小在顯示上永遠為0,存在的意義是在Linux/Unix下提供本地程序間通訊的一種方式 p: 管道檔案 2.UNIX本地套接字使用流程 服務端流程 1.建立本地套接字: unix_socket = socket(AF_UNIX, SOCK_STREAM) 2.繫結套接字檔案: unix_socket.bind("./unix_socket_test") 3.監聽: unix_socket.listen(128) 4.等待客戶端連線: new_client_socket, client_addr = unix_socket.accept() 4.接收連線: recv_data = unix_socket.recv(1024) 5.收發訊息: new_client_socket.send() 6.關閉客戶端套接字: new_client_socket.close() 8.關閉本地套接字: unix_socket.close() 客戶端流程 1.建立本地套接字: unix_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 2.連線本地套接字檔案: unix_client_socket.connect(unix_server_address) 3.傳送訊息: unix_client_socket.send() 4.接收訊息: recv_data = unix_client_socket.recv(1024) 5.關閉套接字: unix_client_socket.close() 3.UNIX應用-客戶端 import socket import sys import traceback def main(): try: # 建立unix本地套接字 unix_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # 檔案已經被服務端建立,且和服務端使用的是同一個socket檔案 unix_server_address = "./unix_socket_test" # 連線本地套接字檔案 unix_client_socket.connect(unix_server_address) except socket.error: traceback.print_exc() sys.exit(1) while True: # 傳送資料 send_data = input("請輸入要發生的資料:") if send_data: unix_client_socket.sendall(send_data.encode("utf-8")) # 接收伺服器傳送過來的資料 recv_data = unix_client_socket.recv(1024) print("接收到的資料為:%s" % recv_data.decode("utf-8")) else: break # 關閉套接字 unix_client_socket.close() if __name__ == "__main__": main() 4.UNIX應用-服務端 import socket import os def main(): # 確定那個檔案作為通訊檔案 unix_server_address = "./unix_socket_test" # 判斷通訊檔案是否存在,如果存在則刪除檔案 if os.path.exists(unix_server_address): os.unlink(unix_server_address) # 建立一個unix本地套接字 unix_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # 繫結本地套接字檔案 unix_socket.bind(unix_server_address) # 監聽 unix_socket.listen(128) # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務 while True: print("等待一個新的客戶端的到來...") new_client_socket, client_addr = unix_socket.accept() print("客戶端' %s '已經到來" % str(client_addr)) # 迴圈目的: 為同一個客服端服務多次 while True: # 接收客戶端傳送過來的請求 recv_data = new_client_socket.recv(1024) # recv解堵塞的兩種方式: 1.客戶端傳送過來資料,2.客戶端呼叫了close if recv_data: print("客戶端' %s '傳送過來的請求是: %s" % (str(client_addr), recv_data.decode("utf-8"))) # 回送資料給客戶端表示響應客戶端的請求 send_data = "----ok----Request accepted" new_client_socket.send(send_data.encode("utf-8")) else: break # 5.關閉客戶端套接字 new_client_socket.close() # 關閉本地套接字 unix_socket.close() if __name__ == "__main__": main()
5.TCP和UDP區別
1.TCP傳輸資料使用位元組流方式傳輸,UDP是資料包 2.TCP會產生粘包現象,UDP不會 3.TCP對網路條件要求高,UDP不需要 4.TCP程式設計可以保證傳輸的可靠性,UDP不保證 5.TCP使用listen和accept,UDP不需要 6.收發訊息 TCP使用recv send sendall # sendall用法和send一樣,只是返回值不同,成功返回None, 失敗則產生異常 UDP使用recvfrom sendto