峰哥解决粘包的方式

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

struct模块

该模块可以把一个类型,如数字,转成固定长度的bytes

>> > struct.pack(‘i‘, 1111111111111)

。。。。。。。。。

struct.error: ‘i‘formatrequires - 2147483648 <= number <= 2147483647  # 这个是范围

import json, struct

# 假设通过客户端上传1T:1073741824000的文件a.txt

# 为避免粘包,必须自定制报头
header = {‘file_size‘: 1073741824000, ‘file_name‘: ‘/a/b/c/d/e/a.txt‘,
          ‘md5‘: ‘8f6fbf8347faa4924a76856701edb0f3‘}  # 1T数据,文件路径和md5值

# 为了该报头能传送,需要序列化并且转为bytes
head_bytes = bytes(json.dumps(header), encoding=‘utf-8‘)  # 序列化并转成bytes,用于传输

# 为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
head_len_bytes = struct.pack(‘i‘, len(head_bytes))  # 这4个字节里只包含了一个数字,该数字是报头的长度

# 客户端开始发送
conn.send(head_len_bytes)  # 先发报头的长度,4个bytes
conn.send(head_bytes)  # 再发报头的字节格式
conn.sendall(文件内容)  # 然后发真实内容的字节格式

# 服务端开始接收
head_len_bytes = s.recv(4)  # 先收报头4个bytes,得到报头长度的字节格式
x = struct.unpack(‘i‘, head_len_bytes)[0]  # 提取报头的长度

head_bytes = s.recv(x)  # 按照报头长度x,收取报头的bytes格式
header = json.loads(json.dumps(header))  # 提取报头

# 最后根据报头的内容提取真实的数据,比如
real_data_len = s.recv(header[‘file_size‘])
s.recv(real_data_len)
# struct的用法# _*_coding:utf-8_*_
# http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
__author__ = ‘Linhaifeng‘
import struct
import binascii
import ctypes

values1 = (1, ‘abc‘.encode(‘utf-8‘), 2.7)
values2 = (‘defg‘.encode(‘utf-8‘), 101)
s1 = struct.Struct(‘I3sf‘)
s2 = struct.Struct(‘4sI‘)

print(s1.size, s2.size)
prebuffer = ctypes.create_string_buffer(s1.size + s2.size)
print(‘Before : ‘, binascii.hexlify(prebuffer))
# t=binascii.hexlify(‘asdfaf‘.encode(‘utf-8‘))
# print(t)

s1.pack_into(prebuffer, 0, *values1)
s2.pack_into(prebuffer, s1.size, *values2)

print(‘After pack‘, binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer, 0))
print(s2.unpack_from(prebuffer, s1.size))

s3 = struct.Struct(‘ii‘)
s3.pack_into(prebuffer, 0, 123, 123)
print(‘After pack‘, binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer, 0))

自定义报头

# 服务端import socket, struct, json
import subprocess

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 就是它,在bind前加

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()

        conn.send(struct.pack(‘i‘, len(back_msg)))  # 先发back_msg的长度
        conn.sendall(back_msg)  # 在发真实的内容

    conn.close()


# 客户端
# _*_coding:utf-8_*_
__author__ = ‘Linhaifeng‘
import socket, time, struct

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‘))

    l = s.recv(4)
    x = struct.unpack(‘i‘, l)[0]
    print(type(x), x)
    # print(struct.unpack(‘I‘,l))
    r_s = 0
    data = b‘‘
    while r_s < x:
        r_d = s.recv(1024)
        data += r_d
        r_s += len(r_d)

    # print(data.decode(‘utf-8‘))
    print(data.decode(‘gbk‘))  # windows默认gbk编码
我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

发送时:

先发报头长度

再编码报头内容然后发送

最后发真实内容

接收时:

先手报头长度,用struct取出来

根据取出的长度收取报头内容,然后解码,反序列化

从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
#服务端定制复杂的报头
import socket, struct, json
import subprocess

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 就是它,在bind前加

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()
# 客户端
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编码

原文地址:https://www.cnblogs.com/dangrui0725/p/9484623.html

时间: 2024-10-08 01:00:21

峰哥解决粘包的方式的相关文章

Python网络编程04/recv原理/高大上版解决粘包方式

目录 Python网络编程04/recv原理/高大上版解决粘包方式 1.昨日内容回顾 2.recv工作原理 3.高大上版解决粘包方式(自定制报头) 3.1 解决思路: 3.2 服务端 3.3客户端 4.基于UDP协议的socket通信 4.1服务端 4.2客户端 Python网络编程04/recv原理/高大上版解决粘包方式 1.昨日内容回顾 1. 通信循环 2. 链接通信循环 3. 远程执行命令: subprocess.Popen() # bytes: 网络传输, 文件存储时. 4. 粘包现象

10.python网络编程(解决粘包问题 part 2)

一.什么时候会产生粘包现象. 只有在使用tcp协议的情况下才会产生粘包现象!udp协议永远不会! 发送端可以1k1k的把数据发送出去,接收端,可以2k2k的的去接收数据,一次可能会接收3k,也有可能1次接收6k. TCP协议是面向流的协议,这也是容易出现粘包问题的原因.而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的.怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方

粘包产生的原因 socket 基于tcp实现远程执行命令(解决粘包)low

# 粘包产生的原因 # 粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的. # 基于tcp协议的套接字会有粘包现象,而基于udp协议的套接字不会产生粘包现象 # tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住:而udp是基于数据报的,即使你输入的是空内容,那也不是空消息,udp协议会帮你封装上消息头(ip+端口的方式),这样就有了消息办界 # 两种情况下会发生粘包 # 1.发送端需要等缓冲区满才发送

Netty解决粘包和拆包问题的四种方案

在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使用的同一个连接,这样就会产生粘包和拆包的问题.本文首先会对粘包和拆包问题进行描述,然后介绍其常用的解决方案,最后会对Netty提供的几种解决方案进行讲解.这里说明一下,由于oschina将"jie ma qi"认定为敏感文字,因而本文统一使用"解码一器"表示该含义 粘包

解决粘包和拆包问题

解决粘包和拆包问题 上一篇我们介绍了如果使用Netty来开发一个简单的服务端和客户端,接下来我们来讨论如何使用解码器来解决TCP的粘包和拆包问题 我们知道,TCP是以一种流的方式来进行网络转播的,当tcp三次握手简历通信后,客户端服务端之间就建立了一种通讯管道,我们可以想象成自来水管道,流出来的水是连城一片的,是没有分界线的. TCP底层并不了解上层的业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分. 所以对于我们应用层而言.我们直观是发送一个个连续完整TCP数据包的,而在底层就可

用readn与written实现解决粘包问题

总结一些我们在平时使用vim编辑器的一些实用功能,后续会一直更新. 1.  visual插件 visual插件其实相当于一个书签作用,比如我们在一篇很长的源代码文件中,我们可以标记一行,然后后来我们再想回到这一行时,只需要一个快捷键就能迅速定位到这一行,非常方便,不用不停地往上或往下翻. 1.1  常用命令 1.  mm标记一个标签: 2.  F2回到被标记的那一行: 3.  连续按两次mm就可以取消标签: 4.  shift+F2可以在几个标签来回切换: 2.  emmet.vim插件 emm

NetworkComms c#通信框架与Java的Netty框架通信 解决粘包问题

上次写了一篇文章  基于networkcomms V3通信框架的c#服务器与java客户端进行通信之Protobuf探讨 其中没有解决粘包问题,抛砖引玉,文章得到了失足程序员 老师的点评,并给出了解决方案:[最多评论]java netty socket库和自定义C#socket库利用protobuf进行通信完整实例(10/591) » 于是马上开始学习,并把c#服务器端换成了我比较熟悉的networkcomms v3 c#通信框架(商业版,本文并不提供) ,以方便与已经存在的系统进行整合. 客户

Socket解决粘包问题1

粘包是指发送端发送的包速度过快,到接收端那边多包并成一个包的现象,比如发送端连续10次发送1个字符'a',因为发送的速度很快,接收端可能一次就收到了10个字符'aaaaaaaaaa',这就是接收端的粘包. 可能我们在平时练习时没觉的粘包有什么危害,或者通过把发送端发送的速率调慢来解决粘包,但在实时通信中,发送端常常是单片机或者其他系统的信息采集机,它们的发送速率是无法控制的,如果不解决接收端的粘包问题,我们无法获得正常的信息. 就以我自己正在做的项目来说,接收端是一台单频指标测量仪,它会把当前测

c# socket 解决粘包,半包

处理原理: 半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码 粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断是否有剩余字节,有的话缓存起来,循环半包处理 客户端接收代码: private void callReceived(object sender, SocketAsyncEventArgs args) { var socket = sender as Socket; var bb = args.Use