粘包现象(存在于tcp中)

Posted fantsaymwq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了粘包现象(存在于tcp中)相关的知识,希望对你有一定的参考价值。

多个包 多个命令的结果 粘到一起了 因为recv(1024)1024限制了导致的结果

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

参考:http://www.cnblogs.com/linhaifeng/articles/6129246.html

粘包底层原理分析:
1、运行一个软件和硬盘、内存、cpu这三个硬件有关

2、启动程序:硬盘程序加载到内存启动一个软件就占一个内存空间
  操作系统本身有一个内存空间
  操作系统所占得到内存空间和软件的内存空间彼此互相隔离

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

 

技术分享图片



3、send、recv的底层原理

应用程序通过send将自己数据发给操作系统,操作系统调用相应硬件中的数据及程序,通过网卡传送给操作系统(recv对应的操作系统),操作系统通过相应程序将数据传送给secv
  1、send发到数据到服务端os的内存 # 慢
  2、os的内存 copy 给程序 # 快
站在应用程序角度上:
  send: 1.数据发给本地的os # 耗时短一些
  recv:1.通知os收数据 2.os内存数据copy到应用程序内存中 # 耗时长一些

4、send recv 总结:
a、不管是recv还是send都不是直接接收对方的数据,而是操作自己的操作系统内存--->不是一个send对应一个recv # 一发可以对应多收 一收对应多发
b、recv:
  wait data 耗时非常长
  copy data
     send:
  copy data
c、数据量比较小 时间间隔比较短就合并成一个包,再发
使用了优化方法(Nagle算法)

5.recv 有关部门建议的不要超过 8192,再大反而会出现影响收发速度和不稳定的情况


解决粘包现象

用struct模块

import struct

# res = struct.pack("i", 1230)  # 第一个参数代表格式i(int)
# print(res, type(res), len(res))
#
# obj = struct.unpack("i", res)
# print(obj[0])

# clienr.recv(4)

res = struct.pack("l", 1200000000)  # 第一个参数代表格式l(long)
print(res, type(res), len(res))

解决粘包解释加报头


解决粘包(简单版)

服务端

import socket
import subprocess
import struct

# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # (如果机器中存在,重新用端口)应对端口占用报错情况
# 2、绑定手机卡
phone.bind(("127.0.0.1", 9909))   # 127.0.0.1本地地址,端口范围0-65535:其中0-1024给操作系统使用

# 3、开机
phone.listen(5)   # 5代表最大挂起连接数

# 4、等电话连接
print("starting...")
while True:  # 循环链接
    conn, client = phone.accept()  # conn套接字对象

# 5、收、发消息
    while True:    # 通讯循环
        try:
            # a、接收命令  (命令:执行系统命令)
            cmd = conn.recv(8096)  # 收1024个字节,接受数据的最大数。单位是bytes
           # if not data: break  # 仅适用于Linux操作系统(客户端断开),win 用try...except
            # b、执行命令,拿到结果
            obj = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            # c、把命令的结果返回给客户端
            # 第一步:制作固定长度的报头(import struct)
            total_size = len(stdout) + len(stderr)
            header = struct.pack("i", total_size)

            # 第二步:把报头(固定长度)发送给客户端
            conn.send(header)

            # 第二步:再发送真实的数据
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionRefusedError:
            break

# 6、挂电话
    conn.close()

# 7、关机
phone.close()

客户端

import socket
import struct

# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2、打电话
phone.connect(("127.0.0.1", 9909))  # phone相当于服务端的conn

# 3、发、收消息
while True:
    # a、发命令
    cmd = input(">> ").strip()
    if not cmd:
        continue
    phone.send(cmd.encode("utf-8"))

    # b、拿命令结果并打印
    # 第一步:先收报头
    header = phone.recv(4)

    # 第二步:从报头中解析出对真实数据的描述信息(数据的长度)
    total_size = struct.unpack("i", header)[0]
    # 第三步:接受真实的数据
    recv_size = 0
    recv_data = b""
    while recv_size < total_size:
        res = phone.recv(1024)
        recv_data += res
        recv_size += len(res)
    print(recv_data.decode("gbk"))   # 系统发回的结果

# 4、关闭
phone.close()

  


 

解决粘包(终极版)

服务端

import socket
import subprocess
import struct
import json

# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # (如果机器中存在,重新用端口)应对端口占用报错情况
# 2、绑定手机卡
phone.bind(("127.0.0.1", 9909))   # 127.0.0.1本地地址,端口范围0-65535:其中0-1024给操作系统使用

# 3、开机
phone.listen(5)   # 5代表最大挂起连接数

# 4、等电话连接
print("starting...")
while True:  # 循环链接
    conn, client = phone.accept()  # conn套接字对象

# 5、收、发消息
    while True:    # 通讯循环
        try:
            # a、接收命令  (命令:执行系统命令)
            cmd = conn.recv(8096)  # 收1024个字节,接受数据的最大数。单位是bytes
           # if not data: break  # 仅适用于Linux操作系统(客户端断开),win 用try...except
            # b、执行命令,拿到结果
            obj = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            # c、把命令的结果返回给客户端
            # 第一步:制作固定长度的报头(import struct)
            header_dic = {
                "filename": "a.text",
                "md5": "xxdxxx",
                "total_size": len(stdout) + len(stderr)
            }
            header_json = json.dumps(header_dic)

            header_bytes = header_json.encode("utf-8")

            # 第二步:先发送报头的长度
            conn.send(struct.pack("i", len(header_bytes)))

            # 第三步:再发报头
            conn.send(header_bytes)

            # 第四步:再发送真实的数据
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionRefusedError:
            break

# 6、挂电话
    conn.close()

# 7、关机
phone.close()

客户端

import socket
import struct
import json

# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2、打电话
phone.connect(("127.0.0.1", 9909))  # phone相当于服务端的conn

# 3、发、收消息
while True:
    # a、发命令
    cmd = input(">> ").strip()
    if not cmd:
        continue
    phone.send(cmd.encode("utf-8"))

    # b、拿命令结果并打印
    # 第一步:先收报头的长度
    header = phone.recv(4)
    header_size = struct.unpack("i", header)[0]

    #第二步:再接收报头信息
    header_bytes = phone.recv(header_size)

    # 第三步:从报头中解析出对真实数据的描述信息
    header_json = header_bytes.decode("utf-8")
    header_dic = json.loads(header_json)
    print(header_dic)
    total_size = header_dic["total_size"]

    # 第四步:接受真实的数据
    recv_size = 0
    recv_data = b""
    while recv_size < total_size:
        res = phone.recv(1024)
        recv_data += res
        recv_size += len(res)
    print(recv_data.decode("gbk"))   # 系统发回的结果

# 4、关闭
phone.close()

  

 




























以上是关于粘包现象(存在于tcp中)的主要内容,如果未能解决你的问题,请参考以下文章

基于tcp协议下粘包现象和解决方案

粘包现象

TCP“粘包”是什么?

TCP“粘包”是什么?

TCP“粘包”是什么?

粘包现象