zoukankan      html  css  js  c++  java
  • 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()
    
  • 相关阅读:
    nodeJS学习(8)--- WS/...开发 NodeJS 项目-节3 <使用 mongodb 完整实例过程>
    nodeJS学习(7)--- WS开发 NodeJS 项目-节2 <安装&设置&启动 mongodb 数据库++遇到的问题>
    nodeJS学习(6)--- Sublime Text3 配置Node.js 开发环境
    nodeJS学习(5) --- sublime Text3 安装使用
    nodeJS学习(4)--- webstorm/...开发 NodeJS 项目-节1
    nodeJS学习(3)--- npm 配置和安装 express4.X 遇到的问题及解决
    二叉查找树-优化版,使用了指针引用
    二叉查找树实现-双向链表
    数据结构-中序转后序
    MySQL 游戏排行榜
  • 原文地址:https://www.cnblogs.com/xjmlove/p/10350575.html
Copyright © 2011-2022 走看看