python网络编程2-黏包问题
一、復習
# ip地址:一臺機器在網絡上的位置 # 公網ip 私網ip # TCP協議:可靠,面向連接的,耗時長#三次握手#四次揮手 # UDP協議:不可靠,無連接,效率高 # ARP協議:通過ip找mac的過程 # ip協議屬于網絡osi中的網絡層 # TCP協議和UDP協議屬于傳輸層 # arp協議屬于數據鏈路層二、黏包(第一條和第二條數據合并發送)
tcp:不會丟包會黏包
udp:會丟包不會黏包
tcp黏包案例:
server_tcp端:
#server端 import socket sk=socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen()conn,addr=sk.accept() while True:cmd=input('>>>')conn.send(cmd.encode('utf-8'))ret1=conn.recv(1024).decode('utf-8')ret2 = conn.recv(1024).decode('utf-8')print(ret1)print(ret2)conn.close() sk.close()client_tcp端:
#client端 import socket import subprocess sk=socket.socket()sk.connect(('127.0.0.1',8090)) while True:cmd=sk.recv(1024).decode('utf-8')ret=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)std_out='stdout:'+(ret.stdout.read()).decode('gbk')std_err='stderr:'+(ret.stderr.read()).decode('gbk')sk.send(std_out.encode('utf-8'))sk.send(std_err.encode('utf-8')) sk.close()運行結果:
udp丟包案例:(udp起server和client)
server_udp端:
import socket sk = socket.socket(type=socket.SOCK_DGRAM) ip_port = ('127.0.0.1',8090) sk.bind(ip_port) msg, addr = sk.recvfrom(10240) while True:cmd = input('>>>')if cmd == 'q':breaksk.sendto(cmd.encode('utf-8'),addr)msg1,addr = sk.recvfrom(20480)print(msg1.decode('utf-8'))msg2, addr = sk.recvfrom(20480)print(msg2.decode('utf-8')) sk.close()client_udp端:
import socket import subprocess sk = socket.socket(type=socket.SOCK_DGRAM) ip_port = ('127.0.0.1',8090) sk.sendto(b'hi',ip_port) while True:cmd,addr = sk.recvfrom(1024)cmd = cmd.decode('utf-8')ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)std_out = 'stdout:'+(ret.stdout.read()).decode('gbk')std_err = 'stderr:'+(ret.stderr.read()).decode('gbk')print(std_out)print(std_err)sk.sendto(std_out.encode('utf-8'),addr)sk.sendto(std_err.encode('utf-8'),addr) sk.close() #不會黏包,會丟包運行結果:
運行結果顯示,如果udp的接收數據量大小滿足發送數據量大小,那么就不會丟包,若是不滿足發送數據量大小,則就會報錯。而不是丟包
三 、黏包的觸發
情況一:發送方的緩存機制:發送端要等緩沖區滿才發送出去。造成黏包(發送數據時間間隔很短,數據很小,會合并一起造成粘包)
如:
#server端 import socket sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr=sk.accept() ret=conn.recv(12) ret2=conn.recv(12) print(ret) print(ret2) conn.close() sk.close()#關閉時會發空消息 # 多個send小的數據連在一起,可能會發生黏包現象,是tcp內部的優化算法引起的 #client端 import socket sk=socket.socket() sk.connect(('127.0.0.1',8080)) sk.send(b'hello') import time # time.sleep(0.01) sk.send(b'world') sk.send(b'dd') sk.close()運行結果:(有時會黏包有時不會)
情況二:接收方的緩沖機制
接收方不及時接收緩沖區的包,造成多個包接收(客戶端發送了一段數據,服務端只接收了一小部分,服務器下次再接收時還是從緩沖區拿上次遺留的數據,產生粘包)
如:
# server端 import socket sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen()conn,addr=sk.accept() ret=conn.recv(2) ret2=conn.recv(10) print(ret) print(ret2) conn.close() sk.close()# 黏包的本職問題:不知道發送數據的長度 # 連續的小數據包會被合并 # client端 import socket sk=socket.socket() sk.connect(('127.0.0.1',8080))sk.send(b'hello,egg') sk.close()運行結果:
總結
黏包現象只發生在tcp協議中:
1.從表面上看,黏包問題主要是因為發送方和接收方的緩存機制、tcp協議面向流通信的特點。
2.實際上,主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的
四、黏包的解決方案
解決方案一:
問題的根源在于,接收端不知道發送端將要傳送的字節流的長度,所以解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把自己將要發送的字節流總大小讓接收端知曉,然后接收端來一個死循環接收完所有數據。
# server # server 下發命令 給client import socket sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr=sk.accept() while True:cmd=input(">>>")if cmd=='q':conn.send(b'q')breakconn.send(cmd.encode('gbk'))num=conn.recv(1024).decode('utf-8')conn.send(b'ok')res=conn.recv(int(num)).decode('gbk')print(res) conn.close() sk.close() # client # 接收server端的命令后在自己的機器上執行 import socket import subprocesssk=socket.socket() sk.connect(('127.0.0.1',8080)) while True:cmd=sk.recv(1024).decode('gbk')if cmd=='q':breakres=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)std_out=res.stdout.read()std_err=res.stderr.read()sk.send(str((len(std_out)+len(std_err))).encode('utf-8'))sk.recv(1024) #oksk.send(std_out)sk.send(std_err) sk.close()# 好處:確定我到底要接收多大的數據 # recv的大小一般不超過4096 # 要在文件中配置一個配置項:就是每次recv的大小 buffer=4096 # 當我們發送大數據量的時候,要明確的告訴接收方要發送多大數據以便接收方能準確接收到所有數據 # 多用在文件傳輸過程中#大文件的傳輸一定是按照字節讀 每次讀固定的字節# 傳輸的過程中 一邊讀一邊傳 接收端:一邊收一邊寫# send大文件之前,告知大小。大小-4096-4096....-->0 文件傳輸完# recv大文件,先接受大小。再recv2048.不會丟 大小-2048-2048 -->0 文件接收完 # 不好的地方:多了一次交互 # 5個g數據 # send 和sendto在超過一定范圍后,都會報錯運行結果:
存在的問題: 程序的運行速度遠快于網絡傳輸速度,所以在發送一段字節前,先用send去發送該字節流長度,這種方式會放大網絡延遲帶來的性能損耗
解決方案進階
剛剛的方法,問題在于我們我們在發送
我們可以借助一個模塊,這個模塊可以把要發送的數據長度轉換成固定長度的字節。這樣客戶端每次接收消息之前只要先接受這個固定長度字節的內容看一看接下來要接收的信息大小,那么最終接受的數據只要達到這個值就停止,就能剛好不多不少的接收完整的數據了。
struct模塊
該模塊可以把一個類型,如數字,轉成固定長度的bytes
>>> struct.pack('i',1111111111111)struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個是范圍 # server 下發命令 給client import socket import struct sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr=sk.accept() while True:cmd=input(">>>")if cmd=='q':conn.send(b'q')breakconn.send(cmd.encode('gbk'))num=conn.recv(4) #4 2048num = struct.unpack('i', num)[0]res=conn.recv(int(num)).decode('gbk') #2048print(res) conn.close() sk.close() # 連續send兩個小數據 # 兩個recv,第一個recv特別小 # 遠程執行命令的程序:ipconfig--> 2000,只接收1024.就會緩存,下次繼續接收上次未接收完的數據#連續send兩個小數據2+8=10 # 2 # 8 #兩個recv,第一個recv特別小 # recv(數據的長度) # 接收server端的命令后在自己的機器上執行 import socket import subprocess import structsk=socket.socket() sk.connect(('127.0.0.1',8080)) while True:cmd=sk.recv(1024).decode('gbk')if cmd=='q':breakres=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)std_out=res.stdout.read()std_err=res.stderr.read()len_num=len(std_out)+len(std_err)num_by=struct.pack('i',len_num)sk.send(num_by) # 4 2048sk.send(std_out) # 1024sk.send(std_err) #1024 sk.close()運行結果:
五、ftp發送視頻
sever:
import socket import struct import json buffer=1024 # ip地址和端口號需要寫在配置文件中 sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen()conn,addr=sk.accept() # 接收 head_len=conn.recv(4) # 報頭長度 head_len=struct.unpack('i',head_len)[0] head_json=conn.recv(head_len).decode('utf-8') head=json.loads(head_json) filesize=head['filesize'] with open(head['filename'],'wb') as f:while filesize:print(filesize)if filesize>=buffer:content=conn.recv(buffer)f.write(content)filesize-=bufferelse:content=conn.recv(filesize)f.write(content)filesize=0break f.close() conn.close() sk.close() #發送端 import socket import os import json import struct sk=socket.socket() sk.connect(('127.0.0.1',8080)) # 發送文件 buffer=2046 # 改成4096文件大小會變:發送和接收時間不匹配 # 讀操作快,寫速度慢。緩沖數據多head={'filepath':r'E:\test','filename': '[反貪風暴3]BD國語.mp4','filesize':None} file_path=os.path.join(head['filepath'],head['filename']) filesize=os.path.getsize(file_path) head['filesize']=filesize json_head=json.dumps(head) #字典轉成了字符串 byte_head=json_head.encode('utf-8') # 字符串轉成了bytes head_len=len(byte_head) # 報頭的長度 pack_len=struct.pack('i',head_len) sk.send(pack_len) # 先發報頭的長度 sk.send(byte_head) # 再發送bytes類型的報頭 with open(file_path,'rb') as f:while filesize:print(filesize)if filesize>=buffer:content=f.read(buffer) # 每次讀取出來的大小sk.send(content)filesize-=bufferelse:content=f.read(filesize)sk.send(content)filesize=0break f.close() sk.close()參考自https://www.cnblogs.com/Eva-J/articles/8244551.html#_label5
?
?
?
總結
以上是生活随笔為你收集整理的python网络编程2-黏包问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iphone最新款手机_苹果用户不换安卓
- 下一篇: python如何实现模拟登录_超详细的P