目錄
2.解決黏包方式二:conn.send("00000100".encode())
1.TCP協議和UDP協議
TCP(Transmission Control Protocol)一種面向連線的、可靠的、傳輸層通訊協議(比如:打電話)
優點:可靠,穩定,傳輸完整穩定,不限制資料大小
缺點:慢,效率低,佔用系統資源高,一發一收都需要對方確認
應用:Web瀏覽器,電子郵件,檔案傳輸,大量資料傳輸的場景
UDP(User Datagram Protocol)一種無連線的,不可靠的傳輸層通訊協議(比如:發簡訊)
優點:速度快,可以多人同時聊天,耗費資源少,不需要建立連線
缺點:不穩定,不能保證每次資料都能接收到
應用:IP電話,實時視訊會議,聊天軟體,少量資料傳輸的場景
2.什麼是socket?
socket的意義:通絡通訊過程中,資訊拼接的工具(中文:套接字)
3.socket正文
1.TCP基本語法
服務端
# ### 服務端 import socket # 1.建立一個socket物件 sk = socket.socket() # 2.繫結對應的ip和埠號(讓其他主機在網路中可以找得到) """127.0.0.1代表本地ip""" sk.bind( ("127.0.0.1",9001) ) # 3.開啟監聽 sk.listen() # 4.建立三次握手 conn,addr = sk.accept() # 5.處理收發資料的邏輯 """recv 接受 send 傳送""" res = conn.recv(1024)# 最多一次接受 1024 位元組 print(res.decode("utf-8")) # 6.四次揮手 conn.close() # 7.退還埠 sk.close()
客戶端
# ### 客戶端 import socket # 1.建立一個socket物件 sk = socket.socket() # 2.與伺服器建立連線 sk.connect( ("127.0.0.1",9001) ) # 3.傳送資料(只能傳送二進位制的位元組流) sk.send("北京昨天迎來了暴雨,如今有車是不行的,還得有船".encode("utf-8")) # 4.關閉連線 sk.close()
2.TCP迴圈發訊息
服務端
# ### 服務端 import socket # 1.建立socket物件 sk = socket.socket() # # 在bind方法之前加上這句話,可以讓一個埠重複使用 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 2.繫結ip和埠號(在網路中註冊該主機) sk.bind( ("127.0.0.1" , 9000) ) # 3.開啟監聽 sk.listen() """ print(conn) print(addr) conn:<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9002), raddr=('127.0.0.1', 53620)> addr:('127.0.0.1', 53620) """ # 5.處理收發資料的邏輯 """send(位元組流)""" """ conn.send("我去北京先買船".encode("utf-8")) """ while True: # 4.三次握手 conn,addr = sk.accept() while True: res = conn.recv(1024) print(res.decode()) strvar = input("請輸入服務端要給客戶端傳送的內容") conn.send(strvar.encode()) if strvar.upper() == "Q": break # 6.四次揮手 conn.close() # 7.退還埠 sk.close()
客戶端
# ### 客戶端 import socket # 1.建立socket物件 sk = socket.socket() # 2.連線服務端 sk.connect( ("127.0.0.1" , 9000) ) # 3.收發資料 """ res = sk.recv(1024) # 一次最多接受1024個位元組 print(res.decode()) """ while True: strvar = input("請輸入您要傳送的內容:") sk.send(strvar.encode()) res = sk.recv(1024) if res == b"q" or res == b"Q": break print(res.decode()) # 4.關閉連線 sk.close()
3.UDP基本語法
服務端
# ### 服務端 import socket # 1.建立udp物件 sk = socket.socket(type=socket.SOCK_DGRAM) # 2.繫結地址埠號 sk.bind( ("127.0.0.1",9000) ) # 3.udp伺服器,在一開始只能夠接受資料 msg,cli_addr = sk.recvfrom(1024) print(msg.decode()) print(cli_addr) # 服務端給客戶端傳送資料 msg = "我是你老孃,趕緊給我回家吃飯" sk.sendto(msg.encode(),cli_addr) # 4.關閉連線 sk.close()
客戶端
# ### 客戶端 import socket # 1.建立udp物件 sk = socket.socket(type=socket.SOCK_DGRAM) # 2.收發資料的邏輯 # 傳送資料 msg = "你好,你是mm還是gg" # sendto( 訊息,(ip,埠號) ) sk.sendto( msg.encode() , ("127.0.0.1",9000) ) # 接受資料 msg,server_addr = sk.recvfrom(1024) print(msg.decode()) print(server_addr) # 3.關閉連線 sk.close()
4.UDP迴圈發訊息
服務端
# ### 服務端 import socket # 1.建立udp物件 sk = socket.socket(type=socket.SOCK_DGRAM) # 2.繫結地址埠號 sk.bind( ("127.0.0.1",9000) ) # 3.udp伺服器,在一開始只能夠接受資料 while True: # 接受訊息 msg,cli_addr = sk.recvfrom(1024) print(msg.decode()) message = input("服務端給客戶端傳送的訊息是?:") # 傳送資料 sk.sendto(message.encode() , cli_addr) # 4.關閉連線 sk.close()
客戶端
# ### 客戶端 import socket # 1.建立udp物件 sk = socket.socket(type=socket.SOCK_DGRAM) # 2.收發資料的邏輯 while True: # 傳送資料 message = input("客戶端給服務端傳送的訊息是?:") sk.sendto(message.encode(), ("127.0.0.1",9000) ) # 接受資料 msg,addr = sk.recvfrom(1024) print(msg.decode("utf-8")) # 3.關閉連線 sk.close()
4.黏包
1.出現黏包的原因
tcp協議在傳送資料時,會出現黏包現象.
1.資料粘包是因為在客戶端/伺服器端都會有一個資料緩衝區,
緩衝區用來臨時儲存資料,為了保證能夠完整的接收到資料,因此緩衝區都會設定的比較大。
2.在收發資料頻繁時,由於tcp傳輸訊息的無邊界,不清楚應該擷取多少長度
導致客戶端/伺服器端,都有可能把多條資料當成是一條資料進行擷取,造成黏包
2.黏包出現的兩種情況
黏包現象一:
在傳送端,由於兩個資料短,傳送的時間隔較短,所以在傳送端形成黏包
黏包現象二:
在接收端,由於兩個資料幾乎同時被髮送到對方的快取中,所有在接收端形成了黏包
總結:
傳送端,包之間時間間隔短 或者 接收端,接受不及時, 就會黏包
核心是因為tcp對資料無邊界擷取,不會按照傳送的順序判斷
3.黏包的應用場景
解決黏包場景:
應用場景在實時通訊時,需要閱讀此次發的訊息是什麼
不需要解決黏包場景:
下載或者上傳檔案的時候,最後要把包都結合在一起,黏包無所謂.
5.解決黏包問題
1.解決黏包方式一:先傳送接下來要傳送資料的大小
服務端
# ### 服務端 import time import socket sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind( ("127.0.0.1",9000) ) sk.listen() conn,addr = sk.accept() # 處理收發資料的邏輯 # 先傳送接下來要傳送資料的大小 conn.send("5".encode()) # 傳送5個位元組 # 發完長度之後,再發資料 conn.send("hello".encode()) conn.send(",world".encode()) conn.close() sk.close()
客戶端
# ### 客戶端 """ 黏包出現的兩種情況: (1) 傳送端傳送資料太快 (2) 接收端接收資料太慢 """ import socket import time sk = socket.socket() sk.connect( ("127.0.0.1",9000) ) time.sleep(2) # 睡2s,讓其接受速度慢一些,製造黏包效果 # 處理收發資料的邏輯 # 先接受接下來要傳送資料的大小 res = sk.recv(1) # res = "5" num = int(res.decode()) # num = 5 # 接受num這麼多個位元組數 res1 = sk.recv(num) # 一次最多隻能接收5個位元組 res2 = sk.recv(1024) print(res1) print(res2) sk.close()
2.解決黏包方式二:conn.send("00000100".encode())
服務端:
# ### 服務端 import time import socket sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind( ("127.0.0.1",9000) ) sk.listen() conn,addr = sk.accept() # 處理收發資料的邏輯 # 先傳送接下來要傳送資料的大小 conn.send("00000100".encode()) # 發完長度之後,再發資料 msg = "hello" * 20 conn.send(msg.encode()) conn.send(",world".encode()) conn.close() sk.close()
客戶端:
# ### 客戶端 """ 黏包出現的兩種情況: (1) 傳送端傳送資料太快 (2) 接收端接收資料太慢 """ import socket import time sk = socket.socket() sk.connect( ("127.0.0.1",9000) ) time.sleep(2) # 處理收發資料的邏輯 # 先接受接下來要傳送資料的大小 res = sk.recv(8) num = int(res.decode()) # 接受num這麼多個位元組數 res1 = sk.recv(num) res2 = sk.recv(1024) print(res1) print(res2) sk.close()
其實,這兩種寫法都存在一定的限制,並非最完美的解決方案
下面介紹一個模組,用來完美的解決黏包現象
3.前戲:struct模組
struct模組裡有兩個方法:
pack :把任意長度數字轉化成具有固定4個位元組長度的位元組流
unpack :把4個位元組值恢復成原來的數字,返回最終的是元組
import struct # pack # i => int 要轉化的當前資料是整型 res1 = struct.pack("i",999999999) print(res1,len(res1)) # b'\xff\xc9\x9a;' 4 res2 = struct.pack("i",1) print(res2,len(res2)) # b'\x01\x00\x00\x00' 4 res3 = struct.pack("i",4399999) print(res3,len(res3)) # b'\x7f#C\x00' 4 # pack 的範圍 -2147483648 ~ 2147483647 21個億左右 res4 = struct.pack("i",2100000000) print(res4,len(res4)) # b'\x00u+}' 4 # unpack # i => 把對應的資料轉換成int整型 tup = struct.unpack("i",res) print(tup) # (2100000000,) print(tup[0]) # 2100000000
4.解決黏包方式三:使用struct模組
服務端
# ### 服務端 import time import socket import struct sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind( ("127.0.0.1",9000) ) sk.listen() conn,addr = sk.accept() # 處理收發資料的邏輯 strvar = input("請輸入你要傳送的資料") msg = strvar.encode() length = len(msg) # 你輸入字串的長度 res = struct.pack("i",length) # 無論長度是多少,res都是固定4個位元組長度的位元組流 print("---",res) # 第一次傳送的是位元組長度 conn.send(res) # 第二次傳送真實的資料 conn.send(msg) # 第三次傳送真實的資料 conn.send("世界真美好123".encode()) conn.close() sk.close()
客戶端
# ### 客戶端 """ 黏包出現的兩種情況: (1) 傳送端傳送資料太快 (2) 接收端接收資料太慢 """ import socket import time import struct sk = socket.socket() sk.connect( ("127.0.0.1",9000) ) time.sleep(2) # 處理收發資料的邏輯 # 第一次接受的是位元組長度 n = sk.recv(4) # 接收到4個位元組長度的位元組流 tup = struct.unpack("i",n) # 將4個位元組長度的位元組流轉化成數字 n = tup[0] # n就是長度 # 第二次接受真實的資料 res = sk.recv(n) print(res.decode()) # 第三次接受真實的資料 res = sk.recv(1024) print(res.decode()) sk.close()
struct如何做到控制接受位元組數的呢?