006---粘包现象分析以及解决粘包问题

粘包

什么是粘包?

须知:只有TCP有粘包现象、UDP永远不会粘包。

socket收发消息的原理

模拟ssh远程执行的命令

# 服务端
import subprocess, socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sk.bind(('127.0.0.1', 8011))

sk.listen(5)

print('starting...1')
while True:
    conn, addr = sk.accept()
    while True:
        try:
            # 收命令
            cmd = conn.recv(1024)
            if not cmd:
                break
            print('客户端发来的数据', cmd.decode('utf-8'))

            # 执行命令,拿到结果
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            res = obj.stdout.read()
            res_error = obj.stderr.read()
            if not res and not res_error:
                conn.send('呵呵呵'.encode('gbk'))
            # 返回执行命令的结果给客户端
            conn.send(res + res_error)  # 可以优化
            print(len(res) + len(res_error))
        except ConnectionResetError:
            break
    conn.close()

sk.close()

# 客户端
import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.connect(('127.0.0.1', 8011))
while True:
    # 发命令
    cmd = input('输入你的cmd:').strip()
    if not cmd: continue
    sk.send(cmd.encode('utf-8'))

    # 接收执行命令的结果
    data = sk.recv(1024)

    print(data.decode('gbk'))

sk.close()



分析

正常的cmd命令ipconfig应该显示完全,可我们的tcp协议模拟的服务器和客户端并没有显示完全。

发送端可以是1k1k的发送数据,而接收端可以2k2k的提取数据,甚至10k10k的提取。也就是说应用程序看到的数据是一个流。

只有tcp有粘包现象,udp永远不会有粘包现象。

udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

分类:

  • 发送端粘包:需要等到缓冲区满了一定字节的数据才发给服务端。造成粘包。发送时间的间隔很短,数据很小,合到一起就粘包了。
# 服务端
import socket

sk = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

sk.bind(('127.0.0.1',8011))

sk.listen(5)

conn,addr = sk.accept()

data1 = conn.recv(1024)
print('第一次',data1)

data2 = conn.recv(1024)
print('第二次',data2)

# 客户端
import socket

client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

client.connect(('127.0.0.1',8011))

client.send('hello'.encode('utf-8'))
client.send('p1111'.encode('utf-8'))
client.send('p1111'.encode('utf-8'))
client.send('p1111'.encode('utf-8'))
  • 接收方粘包(之前的模拟的cmd命令):接收的字节数小于发送方发送的字节数。所以,接收方的缓冲区有积压数据,下次接收就是直接取已残留的数据。

解决粘包问题

为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据。

struct模块

该模块可以把一个类型,如数组转化为固定长度的bytes。

服务端

import subprocess

import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sk.bind(('127.0.0.1', 8005))

sk.listen(5)

print('starting...1')
while True:
    conn, addr = sk.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            if not cmd:
                break
            print('客户端发来的命令:', cmd.decode('utf-8'))

            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            res = obj.stdout.read()
            res_error = obj.stderr.read()

            if not res and not res_error:
                conn.send('呵呵呵'.encode('gbk'))

            length = len(res + res_error)
            print('给客户端发送的执行命令结果的长度:', length)

            import struct

            data_length = struct.pack('i', length)
            conn.send(data_length)
            conn.send(res + res_error)

        except ConnectionResetError:
            break
    conn.close()

sk.close()

客户端

import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.connect(('127.0.0.1', 8005))
while True:
    # 发命令
    cmd = input('输入你的cmd:').strip()
    if not cmd: continue
    sk.send(cmd.encode('utf-8'))

    import struct

    data_length = sk.recv(4)
    length = struct.unpack('i', data_length)[0]
    print('客户端接收服务端发来的数据长度:', length)
    recv_size = 0
    recv_msg = b''
    while recv_size < length:
        recv_msg += sk.recv(1024)
        recv_size = len(recv_msg)

    print('执行结果为:', recv_msg.decode('gbk'))

sk.close()
原文地址:https://www.cnblogs.com/xjmlove/p/10350575.html