ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

第八章 socket网络编程(8):粘包现象以及解决方法(代码完善)

2019-08-04 17:51:00  阅读:255  来源: 互联网

标签:socket 编程 send 粘包 ssh 报头 recv


粘包现象

.recv(1024)坑:当传送来的数据超过1024bytes的时候,因为recv只能一次接受1024byte,传输管道就会积压数据 → 下次recv会继续接收积压的数据 → 这回导致本次send的处理结果可能返回的是上次的结果内容的一部分,

粘包现象:TCP协议是流数据协议(传送的是一个整体的流数据),即一条消息对应多少bytes是不可见的。 这导致接收方接受数据的时候不知道怎么断句,使得数据混乱。这就是所谓的粘包现象

注意:只有TCP有粘包现象,UDP永远不会粘包

参考

粘包详解send 和 recv详解

首先我们强调一下:
应用软件不能直接操作硬件,必须借由OS来调用硬件。所以应用软件的send和recv都是针对os的数据传输(应用层)。
其他底层的网络协议的实现都是由os完全接管的。

  • 粘包总结:
    • 无论send还是recv都是针对本地os的内存进行数据交换而不是直接跟目标服务器进行数据交换

      注意:不是一次send就只能对应一次recv,可以任意次对应任意次

    • recv,send处理:
      • recv:
        • wait data:os等待数据 耗时长(需要一直等待)
        • copy data:从os内存拷贝到数据到应用软件
      • send:
        • send data:从应用软件拷贝数据到os内存
      • 粘包:
        • 接收取数据时,一个包数据过大,使数据有一部分残留在了内存里。
        • 接收取数据时,把多个包的数据当成一个包(Nagle导致的)接收了。
    • TCP流传输使用Nagle优化算法把包合并起来发送:这是粘包的一个原因

      Nagle算法:把数据量小并且时间间隔比较短的包合成一个包发送

  • 解决方法:
    • 明确知道包的bytes数的话,就可以避免粘包了 → 事先告诉接收端包的信息就可以了
解决粘包问题

我们用ssh模拟的程序说明:

我么虽然可以recv的参数设定的很大,但是这也是固定值,最大值也不会超过内存的空间,所以也不是个很好的方案

  • 那么我们分析下怎么做:
    • 发送信息的是对方,所以发送数据的时候
      • 把数据长度发送给对方
      • 再把数据发送给对方

server.py

import socket
import subprocess
import struct
import locale

ssh_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssh_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ssh_server.bind(('127.0.0.1', 8080))
ssh_server.listen(3)

while True:
    conn, addr = ssh_server.accept()
    while True:
        try:
            cmd = conn.recv(1024).decode('utf-8')
            if not cmd: break
            print("excute cmd:", cmd)
            obj = subprocess.Popen(cmd, shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            os_locale = locale.getdefaultlocale()
            os_encode = os_locale[1]
            print(stdout.decode(os_encode))

            # 第一步:生成数据的报头(报头:固定长度):需要用到struct模块
            # 我们首先需要把发送数据的描述信息发送给客户端,但是客户端并不知道这个包是干什么的,而且会和数据的包粘包
            # 所以我们把这段数据作为数据的报头发送
            total_size = len(stdout) + len(stderr)
            header = struct.pack('i', total_size)  # 打包报头,打包返回的数据是一个固定长度(4bytes)的bytes类型
            # struct.pack(模式,报头数据),注意:'i'模式int范围:正负2*10^9之内(因为只有4bytes)
            # 另外有'l'模式:长整型。但是这个模式报头为8bytes。也有范围限制

            # 第二部:发送报头
            conn.send(header)  # 因为是bytes类型所以可以直接发送

            # 把命令结果返回给客户端
            # conn.send(stdout+stderr)  # 这里之前用+连接一起发送,但其实粘包也可以一起发送
            conn.send(stdout)  # 连续发送可以粘包
            conn.send(stderr)  # 与上一行粘包成一个数据包了
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

ssh_server.close()

client.py

import socket
import struct

ssh_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

ssh_client.connect(('127.0.0.1', 8080))

while True:
    # 发命令
    cmd = input(">>>>:").strip()
    if not cmd:continue
    ssh_client.send(cmd.encode('utf-8'))

    # 第一步:先收报头
    header = ssh_client.recv(4)  # 因为报头固定4byte 所以我们收4

    # 第二部:从报头解析出数据的描述(数据的长度)
    total_size = struct.unpack('i', header)[0]  # 解包报头,返回一个元组类型,这里我们第一个元素是长度
    print(total_size)  # 打印一下报头内容

    # 第三部:接受真实收据
    recv_size = 0
    recv_data = b''
    while recv_size < total_size:
        res = ssh_client.recv(1024)
        recv_data += res
        recv_size += len(res)

    print(recv_data.decode('cp932'))  # 因为粘包问题解决了,这里我们直接打印

ssh_client.close()

标签:socket,编程,send,粘包,ssh,报头,recv
来源: https://www.cnblogs.com/py-xiaoqiang/p/11298995.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有