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,寫一個簡易的文件上傳下載的功能,用戶需要登陸認證,認證成功后,客戶端用戶可以選擇上傳或者是下載,
上傳的時候服務端提前設定好上傳文件的路徑,將文件上傳到對應的路徑下,下載文件的時候,服務端將之前設定好的上傳路徑中的所有
文件帶上序號展示給用戶看,用戶輸入文件序號后下載對應的文件,文件下載到客戶端程序的當前路徑下就可以了。

 

 
 
 
 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM