在看粘包之前我們先看一個實例
這個實例是在客戶端輸入指令在服務端執行並返回執行結果
其中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()