python-socket編程(三)粘包

在看粘包之前我們先看一個實例

這個實例是在客戶端輸入指令在服務端執行並返回執行結果

其中subprocess就是將命令交予系統進行運行的模塊

代碼

  • 服務端
import socket
import subprocess

ip_port=('127.0.0.1',8001)
backlog=5
buffersize=1024

tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(backlog)

while True:
    conn,addr = tcp_server.accept()
    while True:
        cmd = conn.recv(buffersize).decode('utf-8')
        print(cmd)
        if cmd == 'exit':
            break
        res = subprocess.Popen(cmd,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE,stdin=subprocess.PIPE)
#表示使用shell來解釋語言,標準輸入,標準輸出和錯誤輸入都輸入到管道,而不是打印到屏幕上
        err = res.stderr.read()
        if err:
            cmd_res = err
        else:
            cmd_res = res.stdout.read()
        conn.send(cmd_res)
    conn.close()
  • 客戶端
import socket

buffersize=1024
ip_port = ('127.0.0.1',8001)

tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_client.connect(('127.0.0.1',8001))
while True:
    msg = input('-->:')
    if not msg:continue
    tcp_client.send(msg.encode('utf-8'))
    if msg == 'exit':
        break
    res = tcp_client.recv(buffersize).decode('utf-8')
    print(res)
tcp_client.close()

當我們執行一個命令,它的返回值大於1024字節的時候,就會被截斷,而當你執行下一個命令的時候,上次沒有展示完的內容會繼續輸出到屏幕上,看起來就像兩次的命令結果粘連到了一起,這就是粘包現象。

產生的原因

首先發送數據和接收數據不是一個實時的過程,發送數據都會首先發送至對方的內核態緩存區中,這個我們前面介紹過。發送方可以一次性發送2048字節的數據,而接收方可以一次收取1024字節數據,分兩次接收,這是由buffersize決定的。也就是說應用程序對一段數據有多少字節,對它來說是不可見的,它只負責從流中取走一段,因此粘包現象就產生了。

兩次發送消息的大小相加比如是2000字節(1300+700)形成一段流,第一次收取了1024沒收取完;第二段收取了第一次消息的200加後一段的700。

而udp就不不同了,udp是面向數據報消息的邊界都是標記好的,收和發都是以消息/次爲單位,因此不會出現粘包的現象。

解決方法

基礎解決的思路

產生粘包的主要原因就是tcp不知道每次消息的邊界,因此只要我們每次發送消息時,提前先發送的消息的長度,然後根據消息的長度來接收消息。

  • 服務端
import socket
import subprocess

ip_port=('127.0.0.1',8001)
backlog=5
buffersize=1024

tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(backlog)

while True:
    conn,addr = tcp_server.accept()
    while True:
        cmd = conn.recv(buffersize).decode('utf-8')
        print(cmd)
        if cmd == 'exit':
            break
        res = subprocess.Popen(cmd,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE,stdin=subprocess.PIPE)
        err = res.stderr.read()
        if err:
            cmd_res = err
        else:
            cmd_res = res.stdout.read()
        ##解決粘包
        length = len(cmd_res)
        conn.send(length)
        client_ready = conn.recv(buffer_size)
        if client_ready == b'ready':
        	conn.send(cmd_res)
    conn.close()
  • 客戶端
import socket

buffersize=1024
ip_port = ('127.0.0.1',8001)

tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_client.connect(('127.0.0.1',8001))
while True:
    msg = input('-->:')
    if not msg:continue
    tcp_client.send(msg.encode('utf-8'))
    if msg == 'exit':
        break
    ###解決粘包
    length = tcp_client.recv(buffersize)
    ###由於命令長度和命令內容發送間隔近,防止粘連,客戶端發送‘ready’字樣
    tcp_client.send(b'ready')
    #size統計接收的長度,msg是接收內容的變量,這是收大長度的內容的方法
    recv_size = 0
    recv_msg = b''
    while recv_size < length:
    	recv_msg += tcp_client.recv(buffer_size)
    	recv_size = len(recv_msg)
    res = recv_msg.decode('utf-8')
    print(res)
tcp_client.close()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章