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 = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/"----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 = https://www.cnblogs.com/tangxuecheng/p/input("請輸入要發生的資料:") tcp_client_socket.send(send_data.encode("utf-8")) # 接收服務器發送過來的資料 recv_data = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/"----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 = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/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 = ("<broadcast>", 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 = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/input("請輸入要發生的資料:") if send_data: unix_client_socket.sendall(send_data.encode("utf-8")) # 接收服務器發送過來的資料 recv_data = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/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 = https://www.cnblogs.com/tangxuecheng/p/"----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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/36406.html
標籤:Python
