1. 程式人生 > >socket-粘包

socket-粘包

1、什麼緩衝區,為什麼會有緩衝區?緩衝區是指socket通訊時,收發命令時的一箇中間存放命令的儲存空間因為資料被輸出後在處理的時候需要一定的時間,
為了輸入接著輸入零時差,就
需要緩衝了,先預讀並處理一部分資訊,然後開始輸出,在輸出的同時進行後面的的處理,然後等緩衝的部分輸出完後,另一部分的資料也處理完畢了,
就可以接著
輸出了,如果沒有這個緩衝區,就會很卡輸入輸出的緩衝區一般是8k

tcp粘包演示(一):

先從上面粘包現象中的第一種開始: 接收方沒有及時接收緩衝區的包,造成多個包接收(客戶端傳送了一段資料,服務端只收了一小部分,服務端下次再收的時候還是從緩衝區拿上次遺留的資料,產生粘包) 
  server端程式碼示例:   cket import *
import subprocess

ip_port=('127.0.0.1',8080)
BUFSIZE=1024

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)

while True:
    conn,addr=tcp_socket_server.accept()
    print('客戶端>>>',addr)

    while True:
        cmd=conn.recv(BUFSIZE)
        if len(cmd) == 0:break

        res=subprocess.Popen(cmd.decode('gbk'),shell=True,
                         stdout=subprocess.PIPE,
                         stdin=subprocess.PIPE,
                         stderr=subprocess.PIPE)

        stderr=res.stderr.read()
        stdout=res.stdout.read()
        conn.send(stderr)
        conn.send(stdout)   client端程式碼示例:   ort = ('127.0.0.1',8080)
size = 1024
tcp_sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res = tcp_sk.connect(ip_port)
while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    tcp_sk.send(msg.encode('utf-8'))
    act_res=tcp_sk.recv(size)
    print('接收的返回結果長度為>',len(act_res))
    print('std>>>',act_res.decode('gbk')) #windows返回的內容需要用gbk來解碼,因為windows系統的預設編碼為gbk  

tcp粘包演示(二):傳送端需要等緩衝區滿才傳送出去,造成粘包(傳送資料時間間隔很短,資料也很小,會合到一起,產生粘包)

server端程式碼示例:(如果兩次傳送有一定的時間間隔,那麼就不會出現這種粘包情況,試著在兩次傳送的中間加一個time.sleep(1))

from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)
conn,addr=tcp_socket_server.accept()
data1=conn.recv(10)
data2=conn.recv(10)

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()

client端程式碼示例:

import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# res=s.connect_ex(ip_port)
res=s.connect(ip_port)
s.send('hi'.encode('utf-8'))
s.send('meinv'.encode('utf-8'))

 

udp粘包演示:注意:udp是面向包的,所以udp是不存在粘包的

粘包的原因:主要還是因為接收方不知道訊息之間的界限,不知道一次性提取多少位元組的資料所造成的

 

粘包的解決方案

解決方案(一):

 問題的根源在於,接收端不知道傳送端將要傳送的位元組流的長度,所以解決粘包的方法就是圍繞,如何讓傳送端在傳送資料前,把自己將要傳送的位元組流總大小讓接收端知曉,然後接收端發一個確認訊息給傳送端,然後傳送端再發送過來後面的真實內容,接收端再來一個死迴圈接收完所有資料。   server端程式碼 import socket,subprocess
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

s.bind(ip_port)
s.listen(5)

while True:
    conn,addr=s.accept()
    print('客戶端',addr)
    while True:
        msg=conn.recv(1024)
        if not msg:break
        res=subprocess.Popen(msg.decode('utf-8'),shell=True,\
                            stdin=subprocess.PIPE,\
                         stderr=subprocess.PIPE,\
                         stdout=subprocess.PIPE)
        err=res.stderr.read()
        if err:
            ret=err
        else:
            ret=res.stdout.read()
        data_length=len(ret)
        conn.send(str(data_length).encode('utf-8'))
        data=conn.recv(1024).decode('utf-8')
        if data == 'recv_ready':
            conn.sendall(ret)
    conn.close()   client端程式碼示例: import socket,time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))
    length=int(s.recv(1024).decode('utf-8'))
    s.send('recv_ready'.encode('utf-8'))
    send_size=0
    recv_size=0
    data=b''
    while recv_size < length:
        data+=s.recv(1024)
        recv_size+=len(data)


    print(data.decode('utf-8'))   解決方案(二):     通過struck模組將需要傳送的內容的長度進行打包,打包成一個4位元組長度的資料傳送到對端,對端只要取出前4個位元組,然後對這四個位元組的資料進行解包,拿到你要傳送的內容的長度,然後通過這個長度來繼續接收我們實際要傳送的內容。不是很好理解是吧?哈哈,沒關係,看下面的解釋~~        為什麼要說一下這個模組呢,因為解決方案(一)裡面你發現,我每次要先發送一個我的內容的長度,需要接收端接收,並切需要接收端返回一個確認訊息,我傳送端才能發後面真實的內容,這樣是為了保證資料可靠性,也就是接收雙方能順利溝通,但是多了一次傳送接收的過程,為了減少這個過程,我們就要使struck來發送你需要傳送的資料的長度,來解決上面我們所說的通過傳送內容長度來 解決粘包的問題。 server端: import socket,struct,json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(('127.0.0.1',8080))
phone.listen(5)

while True:
    conn,addr=phone.accept()
    while True:
        cmd=conn.recv(1024)
        if not cmd:break
        print('cmd: %s' %cmd)

        res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        err=res.stderr.read()
        print(err)
        if err:
            back_msg=err
        else:
            back_msg=res.stdout.read()

        headers={'data_size':len(back_msg)}
        head_json=json.dumps(headers)
        head_json_bytes=bytes(head_json,encoding='utf-8')

        conn.send(struct.pack('i',len(head_json_bytes))) #先發報頭的長度
        conn.send(head_json_bytes) #再發報頭
        conn.sendall(back_msg) #在發真實的內容

    conn.close()   client端: from socket import *
import struct,json

ip_port=('127.0.0.1',8080)
client=socket(AF_INET,SOCK_STREAM)
client.connect(ip_port)

while True:
    cmd=input('>>: ')
    if not cmd:continue
    client.send(bytes(cmd,encoding='utf-8'))

    head=client.recv(4)
    head_json_len=struct.unpack('i',head)[0]
    head_json=json.loads(client.recv(head_json_len).decode('utf-8'))
    data_len=head_json['data_size']

    recv_size=0
    recv_data=b''
    while recv_size < data_len:
        recv_data+=client.recv(1024)
        recv_size+=len(recv_data)

    #print(recv_data.decode('utf-8'))
    print(recv_data.decode('gbk')) #windows預設gbk編碼   socketserver模組實現併發 服務端程式碼示例: import socketserver
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "127.0.0.1", 9999

    # 設定allow_reuse_address允許伺服器重用地址
    socketserver.TCPServer.allow_reuse_address = True
    # 建立一個server, 將服務地址繫結到127.0.0.1:9999
    #server = socketserver.TCPServer((HOST, PORT),Myserver)
    server = socketserver.ThreadingTCPServer((HOST, PORT),Myserver)
    # 讓server永遠執行下去,除非強制停止程式
    server.serve_forever()
  客戶端程式碼示例: import socket

HOST, PORT = "127.0.0.1", 9999
data = "hello"

# 建立一個socket連結,SOCK_STREAM代表使用TCP協議
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))          # 連結到客戶端
    sock.sendall(bytes(data + "\n", "utf-8")) # 向服務端傳送資料
    received = str(sock.recv(1024), "utf-8")# 從服務端接收資料

print("Sent:     {}".format(data))
print("Received: {}".format(received))
   
4、什麼是粘包? socket 中造成粘包的原因是什麼? 哪些情況會發生粘包現象?
TCP粘包是指傳送方傳送的若干包資料包到接收方接收時粘成一包,從接收緩衝區看,後一包資料的頭緊接著前一包資料的尾
(1)傳送方原因
TCP預設會使用Nagle演算法。而Nagle演算法主要做這兩件事1>只有上一個分組得到確認,才會傳送下一個分組;2>收集多個小分組,再確認到來時一起傳送
所以是Nagle演算法造成了傳送方造成了粘包現象
(2)接收方原因
TCP接收到分組時,並不會立刻送至應用層處理,或者說,應用層並不會立即處理;實際上,TCP將收到的分組儲存至接收快取裡,然後
應用程式主動從快取裡讀收到的分組。這樣一來,如果TCP接收分組的速度大於應用程式讀分組的速度,多個包就會存至快取,應用程式
讀時,就會讀到多個首尾相接粘到一起的包

哪些情況會發生粘包現象?
(1)傳送端需要等待緩衝區滿才傳送出去,造成粘包
(2)接收方不及時接收緩衝區的包,造成多個包接收

5、用tcp協議下的socket,寫一個簡易的檔案上傳下載的功能,使用者需要登陸認證,認證成功後,客戶端使用者可以選擇上傳或者是下載,
上傳的時候服務端提前設定好上傳檔案的路徑,將檔案上傳到對應的路徑下,下載檔案的時候,服務端將之前設定好的上傳路徑中的所有
檔案帶上序號展示給使用者看,使用者輸入檔案序號後下載對應的檔案,檔案下載到客戶端程式的當前路徑下就可以了。