1. 程式人生 > >Python----socket 解決粘包

Python----socket 解決粘包

粘包出現的原因:

UDP沒有粘包是因為UDP是面向訊息的,TCP出現是因為TCP的工作原理出現的粘包現象。TCP是面向流的,沒有起點和結尾,不知道一條訊息有多少位元組,UDP傳送訊息會發送訊息的長度,這也就是為什麼UDP傳送訊息為空時不會阻塞,只有緩衝區為空時才會阻塞recv()函式。面向流的訊息(TCP)是無邊界的,面向報文的訊息(UDP)是有邊界的。只有TCP有粘包現象(nagle演算法的存在)。TCP套接字沒有一收一發的規矩,而UDP有這個規則。為什麼說tcp可靠是因為每次發訊息都會從客戶端返回一個ack,才會刪除自己快取的資訊。

粘包出現的兩種方式:

1、服務端出現粘包:當傳送的訊息資料的間隔短,會將幾次的分開的訊息一次性

讀出來。產生粘包。

服務端:一次性收到‘hai’。

import socket


ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
while 1:
    con, address = ser.accept()
    res = con.recv(1024)
    print(res)


>>> b'hai'

客戶端:分三次傳送'h','a','i'.

import socket

p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
while 1:
    p.send('h'.encode('utf-8'))
    p.send('a'.encode('utf-8'))
    p.send('i'.encode('utf-8'))

2、客戶端出現粘包:資料無法一次性讀完

客戶端:兩次接收都是上次的資料。

import socket

p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
print(p.recv(2))
print(p.recv(2))

>>> b'12'
>>> b'34'

服務端:一次性發送大量的資料

import socket


ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
con, address = ser.accept()
con.send('123456987845641321545645641654564165132'.encode('utf-8'))

解決粘包的方案:

服務端:

import socket
import subprocess

# 配置資訊
ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024

# 宣告套接字型別
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
# 連線迴圈
while 1:
    con, address = ser.accept()
    # 通訊迴圈
    while 1:
        try:
            msg = con.recv(buffer_size)
            print('伺服器收到訊息', msg.decode('utf-8'))
            if msg.decode('utf-8') == '1':
                con.close()
            # 子程序Popen的方式去執行一個命令,輸出的結果放到管道中
            res = subprocess.Popen(msg.decode('utf-8'), shell=True, stdout=subprocess.PIPE)
            # 從管道中讀取資訊
            fin = res.stdout.read().decode('gbk')
            # 傳送資料位元組長度,防止粘包
            bit = fin.encode('utf-8').__sizeof__()
            con.send(str(bit).encode('utf-8'))  # 這兩次粘一起,第一個send用固定的位元組接受,就不會出現粘包
            con.send(fin.encode('utf-8'))
        except Exception as e:
            break

客戶端:

import socket

p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
while 1:
    msg = input('please input')
    # 防止傳送空訊息
    if not msg:
        continue
    # 輸入1退出
    if msg == '1':
        break
    # 傳送訊息時編碼
    p.send(msg.encode('utf-8'))
    # 接收需要正式接受多少位元組長度的資料長度,防止粘包
    bit = p.recv(24)
    # 接收資料
    data = p.recv(int(bit.decode('utf-8')))
    print(data.decode('utf-8'))
p.close()

TIPS:

✳:result = subprocess.Popen('字串的命令',shell=True,stdout=subprocess.Pipe,stdin=subprocess.Pipe,stderr=subprocess.Pipe)

shell=True  是否執行cmd,stdout、stdin、stderr=subprocess.Pipe  標準輸出、標準輸入、標準錯誤都輸出到管道內,預設丟給螢幕。

stdout.out.read() 讀出資料。

✳:傳送長度時也可以用struct模組:pack('i',資料)  壓包   , unpack('i',資料)  解包

‘ i ’表示int型,‘ch’表示字元等等。。。。

✳:s.connect_ex()函式是connect拓展出錯時不會報異常,返回異常.
      s.getsockname() 獲取當前套接字的地址.
      s.getpeername()連線到當前套接字的遠端地址.

✳:兩個send中間加一個rece就可以解決粘包

✳:sendall方法就是反覆呼叫send方法直到檔案傳輸完畢,如果中間發生錯誤就不會返回穿了多少資料.

✳:一次send最大8k,當傳送一個較大的檔案時,不可能一次性發過去因為記憶體不夠用,所以每次傳送8k的資料8192個位元組。

 偏函式:

只能繫結第一個引數的成為固定值

from functools import partial

def add(x,y):
    return x+y

func = partial(add,1)  # 繫結一個引數預設值為一
print(func(1))