六. 網絡編程(解決黏包TCP)

一 .解決黏包TCP(shell

 1.解決方案一json

問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞,
如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據。
存在的問題:
程序的運行速度遠快於網絡傳輸速度,因此在發送一段字節前,先用send去發送該字節流長度,這種方式會放大網絡延遲帶來的性能損耗
剛剛的方法,問題在於咱們咱們在發送

咱們能夠藉助一個模塊,這個模塊能夠把要發送的數據長度轉換成固定長度的字節。
這樣客戶端每次接收消息以前只要先接受這個固定長度字節的內容看一看接下來要接收的信息大小,
那麼最終接受的數據只要達到這個值就中止,就能恰好很少很多的接收完整的數據了
server import  socket
server=socket.socket()
server.bind(("192.168.59.1",8100))
server.listen(5)
conn,addr=server.accept()
while True:
        aa = input("請輸入命令:").strip()
        conn.send(aa.encode("gbk"))
        num = conn.recv(1024).decode("utf-8")
        conn.send(b"ok")
        res = conn.recv(int(num)).decode("gbk")
        print(res,"1111111111111111111111111111111111111111111111111")

conn.close()
server.close()
client import socket
import  subprocess
client=socket.socket()
while True:
    client.connect(("192.168.59.1", 8100))
    cmd = client.recv(1024).decode("utf-8")
    res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
    stdout = res.stdout.read()
    stderr = res.stderr.read()
    client.send(str(len(stderr) + len(stdout)).encode("utf-8"))
client.recv(1024) client.send(stdout) client.send(stderr) client.close()
上面代碼 好處:
肯定了咱們到底要接收多大的數據
要在文件中配置一個配置項 就是每一次 recv 的大小   bufer=4096 最大
當咱們要發生大數據時候 要明確知道發送的數據大小 告訴對方方便準確的接收因此數據 也就是這裏的 recv

應用場景:
 多用於文件傳輸過程當中
 大文件傳輸 必定是按照字節讀取 每一次讀固定字節
 傳輸的過程當中 一邊讀 一邊傳 接收端一邊收 一邊寫
 send 這個文件以前 35000字節   send(4096)  35000-4069-4039----->0
 recv 這個文件 recv 35000字節 recv(2048)      35000-2048 ---->0

很差:
的地方 多了一次交互
send  sendto  交互過多會致使 程序內存管理 佔用過多

 二 .解決黏包TCP struct模塊(二)緩存

1.該模塊能夠把一個類型,如數字,轉成固定長度的bytes網絡

 # struct 該模塊能夠把一個類型,如數字,轉成固定長度的bytes
import struct
# 藉助struct模塊,咱們知道長度數字能夠被轉換成一個標準大小的4字節數字。所以能夠利用這個特色來預先發送數據長度。
# 爲何 要轉換成固定長度的bytes
 #  aa = struct.pack('i') i 表明int 就是即將要把一個數字轉換成固定長度bytes類型

#一次性發送4096
#       發送時                                                接收時
# 先發送struct轉換好的數據長度4字節          先接受4個字節使用struct轉換成數字來獲取要接收的數據長度
# 再發送數據                              再按照長度接收數據

ret=struct.pack('i',4096)      #     i 表明int 就是即將要把一個數字轉換成固定長度bytes類型      打包
print(ret)  # b'\x00\x10\x00\x00'

num=struct.unpack('i',ret)    #  struct.unpack 解包 的結果必定是元組
print(num)   # (4096,)
print(num[0]) #  4096
# 若是發送數據時候
# 先發送長度   先接收長度
 SERVER # TCP 黏包
    # 連續 send 兩個小數據
    # 兩個 recv 第一個特別小
    # 本質: 你不知道到底接收多大的數據
# 解決問題 例如
# tcp 完美解決了黏包問題
import socket
import struct
server=socket.socket()
server.bind(('127.0.0.1',8700))
server.listen(5)
conn, addr =server.accept()
while True:
      cmd=input("》》》")
      if cmd=="q":
          conn.send(b'q')
          break
      conn.send(cmd.encode("gbk"))
      num=conn.recv(4)      # 4

      nn=struct.unpack('i',num)[0]  # 2048
      res=conn.recv(int(nn)).decode("gbk")   # 2048
      print(res)
conn.close()
server.close()
CLIENT import socket
import subprocess
import struct

s=socket.socket()
s.connect(('127.0.0.1',8700))

while True:
   cmd=s.recv(1024).decode('gbk')
   if cmd=="q":
       break
   res = subprocess.Popen(cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
   std_err = res.stderr.read()
   std_out = res.stdout.read()

   len_a=len(std_out)+len(std_err)

   num_by=struct.pack('i',len_a)

   ret=s.send( num_by)   # 4    2048
   s.send(std_err)# 1024
   s.send(std_out) #1024  4+2048
   print(ret)
s.close()

 

                                                                                   

2. 自定製報頭(使用struct)socket

協議  報文 報頭

文件傳輸

  文件名字

  文件大小

  文件類型

  存儲路徑

例如:
head={'filemane':'tex.txt','filesize':60000,'filetype':'txt','filepath':r'\user\bin'}
      
報頭 長度                  接收4個字節

send(head) 報頭              根據這4個字節獲取報頭
   
send(file)報文                 從報頭中獲取filesize 而後根據filesize接收文件


協議就是一堆報文和報頭  ------字節
SERVER 視頻上傳 import  socket
import struct
import json
buffer=1024   # 4096
server=socket.socket()
server.bind(('127.0.0.1',8600))
server.listen()
conn,addr=server.accept()

# 接收
head_len=conn.recv(4) 注意接收到的數據是元祖

datel=struct.unpack('i',head_len)[0]   

json_head=conn.recv(datel).decode("utf-8")

head=json.loads(json_head)

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-=buffer
        else:
            content=conn.recv(filesize)
            f.write(content)
            break

conn.close()
server.close()
CLIENT 視頻上傳 # 發送端
import os
import socket
import json
import  struct
sk=socket.socket()
sk.connect(('127.0.0.1',8600))
buffer=1024  # 4096
# 發送文件

head={'filepath':r'D:\Vidoe\vide',
      'filename':r'aa.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)     #  把字典轉換成了str
# print(json_head)
# print(type(json_head))
bytes_head=json_head.encode("utf-8")  #  字符串轉換成bytes
# print(bytes_head)
# print(type(bytes_head))


head_len=len(bytes_head) # 計算hend長度
# print(head_len)
pack_len=struct.pack('i',head_len)
# print(pack_len)

sk.send(pack_len)  # 先發送報頭的長度  注意發送過去是元祖類型數據
sk.send(bytes_head)   # 再發送bytes類型的報頭
with open(file_path,'rb')as f:
    while filesize:
      print(filesize)
      if filesize>=buffer:
        content = f.read(buffer)  # 每次讀出的內容
        sk.send(content)
        filesize-=buffer
      else:
        content=f.read(filesize)
        sk.send(content)
        break

sk.close()

# 5.31 MB (5,575,110 字節)  原視頻
# 5.16 MB (5,415,366 字節)  上傳的視頻

文件上傳案例 tcp

server1 不黏包 多發送一次send 方法一 import  socket
import  json
import  os
server=socket.socket()
server.bind(("192.168.59.1",8600))
server.listen(5)
conn,addr=server.accept()

ret_str=conn.recv(1024).decode("utf-8")
ret=json.loads(ret_str)

file_name=ret.get("filename")
action=ret.get("action")
file_size=ret.get("file_size")
conn.sendall(b"200")

with open(file_name,"wb") as f1:
    conn_len = 0
    while file_size>conn_len:
        msg=conn.recv(1024)
        conn_len+=len(msg)
        f1.write(msg)
        print(f'文件大寫{file_size}---文件傳輸大小{conn_len}')

print("讀取失敗了")
client1 不黏包 多發送接收次recv' 方法一
import  socket
import json
import os

client=socket.socket()
client.connect(("192.168.59.1",8600))
while True:
    cmd=input("請輸入命令:")
    action,filename=cmd.strip().split(" ")
    flie_size=os.path.getsize(filename)
    ret={"action":action,"filename":filename,"file_size":flie_size}

    ret_str=json.dumps(ret).encode("utf-8")
    client.sendall(ret_str)

    code=client.recv(1024).decode("utf-8")
    if code=="200":
        with open(filename,"rb")as f1:
            for line in  f1:
                client.sendall(line)

    else:
        print("接收失敗啦啦啦啦")

 

server使用 struct

import  json,socket,struct
ser=socket.socket()
ser.bind(("192.168.59.1",8600))
ser.listen(5)
conn,addr=ser.accept()
ret=conn.recv(4)
pik=struct.unpack('i',ret)[0]
ret_b=conn.recv(pik).decode("utf-8")
ret_len=json.loads(ret_b)

filename=ret_len.get("filename")
action=ret_len.get("action")
file_size=ret_len.get("file_size")
buff=0
with open("tup/"+filename,"wb") as f1:
    while file_size>buff:
        cont=conn.recv(1024)
        buff+=len(cont)
        f1.write(cont)
client使用 struct
import socket cli=socket.socket() import os import json,struct cli.connect(("192.168.59.1",8600)) cmd=input("輸入指令和文件:") # act aa.jpg action,filename=cmd.strip().split(" ") file_size=os.path.getsize(filename) ret={ "action":action, "filename":filename, "file_size":file_size } ret_str=json.dumps(ret).encode("utf-8") ret_pik=struct.pack("i",len(ret_str)) cli.sendall(ret_pik) cli.sendall(ret_str) with open(filename,"rb")as f1: for line in f1: cli.sendall(line)

文件上傳(使用hashlib 驗證文件一次性)ide

server   aa 11.txt import  json,socket,struct,hashlib
ser=socket.socket()
ser.bind(("192.168.59.1",8600))
ser.listen(5)
conn,addr=ser.accept()
ret=conn.recv(4)
pik=struct.unpack('i',ret)[0]
ret_b=conn.recv(pik).decode("utf-8")
ret_len=json.loads(ret_b)

filename=ret_len.get("filename")
action=ret_len.get("action")
file_size=ret_len.get("file_size")
buff=0
md=hashlib.md5()
with open("tup/"+filename,"wb") as f1:
    while file_size>buff:
        cont=conn.recv(1024)
        buff+=len(cont)
        f1.write(cont)
        md.update(cont)

print("接收成功")
conn.sendall(b"ok")
print(md.hexdigest())
val_md=md.hexdigest()
cli_md5=conn.recv(1024).decode("utf-8")
if cli_md5==val_md:
    conn.sendall(b"203")
else:
    conn.sendall(b"208")
client aa 11.txt import hashlib
import socket,json,os
cli=socket.socket()
import os
import json,struct
cli.connect(("192.168.59.1",8600))

cmd=input("輸入指令和文件:") # act aa.jpg
action,filename=cmd.strip().split(" ")
file_size=os.path.getsize(filename)
ret={
    "action":action,
    "filename":filename,
    "file_size":file_size

}
md5=hashlib.md5()
ret_str=json.dumps(ret).encode("utf-8")
ret_pik=struct.pack("i",len(ret_str))
cli.sendall(ret_pik)
cli.sendall(ret_str)

with open(filename,"rb")as f1:
        for line in f1:
            cli.sendall(line)
            md5.update(line)

data=cli.recv(1024)
print(data.decode("utf-8"))
print(md5.hexdigest())
val_md=md5.hexdigest()
cli.sendall(val_md.encode("utf-8"))
is_val=cli.recv(1024).decode("utf-8")
if is_val=="203":
    print("完整性")
else:
    print("文件上次失敗")

總結性能

     能夠經過struct模塊來定製協議
解決黏包 問題

  爲何會出現黏包現象
  首頁只有在tcp協議中才會黏包現象

  只是由於tcp協議是面向流的協議

  在發送的數據傳輸過程當中還有緩存機制來避免數據丟包

  所以 在連續發送小數據的時候 以及接收大小不符的時候容易黏包 現象

  本質 仍是由於咱們在接收數據的時候不知道發送的數據的長短


解決黏包問題

    在傳輸大量數據以前先告訴接收端要發送的數據長短

     能夠經過struct模塊來定製協議

struct 模塊
    pack   unpack

   模式  i
      pack以後的長度  4個字節
     unpack 以後娜到的數據是一個元組 元組的第一個值是pack的值
相關文章
相關標籤/搜索