首页 > 其他分享 >高手过招--论TCP之粘包的解决方法

高手过招--论TCP之粘包的解决方法

时间:2024-08-23 09:04:15浏览次数:8  
标签:1024 -- res cmd TCP 粘包 length client tcp

粘包,就是查询的内容都粘到一起了,比如客户端发送ipconfig /all命令到服务端,客户端的只收取一次服务端的返回结果,且设置为一次只能取出1024个字节的数据。
假设ipconfig /all这条命令的返回结果大小是2048个字节,这就意味着还有1024没有取出来,仍然会保存在客户端的缓存中。此时客户端再发送一个dir命令到服务端,
客户端收到的返回信息就是剩下的ipconfig /all的结果,因为TCP数据传输是队列形式,原理就是先进先出。这时候并没有出现dir的真正结果,这种情况就叫做粘包。
有人可能会说,那为什么不加大客户端的可接收范围呢,比如把1024改成2048,大哥,你客户端知道自己发送的命令执行结果的大小吗?显然不知道,不知道设置个屁呀!
那么问题来了,到底该怎么解决粘包问题呢?、
很简单,思路就是我们只需要拿到ipconfig /all命令的大小就可以知道该收多少字节了,拿到了命令执行结果的字节,我们就知道该收取多少了,这时候就可以改客户端的1024了对不对,改成2048??
如果你这么干,我想对你说三个字:臭傻逼。

应该写个循环,多收几次,直到收完结束循环不就解决问题了吗??
来吧,上代码:


服务端:
import socketserver
import subprocess
ip_port=('127.0.0.1',8080)

class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                cmd=self.request.recv(1024).decode('utf-8')
                if not cmd:break
                res=subprocess.Popen(cmd,shell=True,
                                    stderr=subprocess.PIPE,
                                    stdout=subprocess.PIPE,
                                    stdin=subprocess.PIPE)
                res_err=res.stderr.read()
                if res_err:
                    res_cmd=res_err
                res_cmd=res.stdout.read()
                if not res_cmd:
                    res_cmd='空命令执行了'.encode('gbk')
                cmd_length=len(res_cmd)
                self.request.sendall(str(cmd_length).encode('gbk'))
                data=self.request.recv(1024)
                self.request.sendall(res_cmd)
            except Exception as e:
                break

s=socketserver.ThreadingTCPServer(ip_port,Myserver)
print('server starting...')
s.serve_forever()

客户端:
import socket
ip_port=('127.0.0.1',8080)

class Myclient:
    def __init__(self):
        self.sock=socket.socket()
        self.sock.connect(ip_port)
        self.exec()
    def exec(self):
        while True:
            cmd=input('输入命令:').strip()
            if not cmd:continue
            if cmd == 'quit':break
            self.sock.send(cmd.encode('utf-8'))

            res_cmd=0
            data=b''
            cmd_length=self.sock.recv(1024).decode('gbk')
            self.sock.send('ok'.encode('utf-8'))
            while res_cmd < int(cmd_length):
                data+=self.sock.recv(1024)
                res_cmd += len(data)
            #print(res_cmd)
            print(data.decode('gbk'))


m=Myclient()

上面是用类的方式写的,解决了多线程问题,可以同时开多个客户端且互不影响。
下面是原始的写法:

服务端:
from socket import *
import subprocess
ip_port=('127.0.0.1',8003)
buffer_size=1024
backlog=5

tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(backlog)

while True:
    print('服务端开始运行...')
    conn,addr=tcp_server.accept()
    print('客户端已连接上来...')
    while True:
        try:
            cmd_msg=conn.recv(1024).decode('utf-8')
            if not cmd_msg:break
            res=subprocess.Popen(cmd_msg,shell=True,
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stdin=subprocess.PIPE)
            res_err=res.stderr.read()
            if res_err:
                res_cmd=res_err
            res_cmd=res.stdout.read()
            if not res_cmd:
                res_cmd='空命令已执行,只是没结果.'.encode('gbk')
            cmd_length=len(res_cmd)
            conn.send(str(cmd_length).encode('utf'))
            data=conn.recv(1024)
            conn.send(res_cmd)
        except Exception:
            break
    conn.close()
tcp_server.close()

客户端:
from socket import *
ip_port=('127.0.0.1',8003)
buffer_size=1024


tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    cmd_msg=input('输入命令:')
    if not cmd_msg:continue
    if cmd_msg == 'quit':break
    tcp_client.send(cmd_msg.encode('utf-8'))
    #收到的命令执行结果大小占多少字节
    cmd_length=tcp_client.recv(1024).decode('utf')

    '''
       下面这句话其实是为了防止粘包,因为粘包的情况有两种:
       (1) 待读取结果太大超出了1024的范围
       (2) 前后两条send命令且他们中间没有recv
    '''
    tcp_client.send('ok'.encode('utf-8'))
    client_cmd_length=0
    res_cmd=b''
    while client_cmd_length < int(cmd_length):
        res_cmd += tcp_client.recv(1024)
        client_cmd_length = len(res_cmd)
    print(res_cmd.decode('gbk'))
tcp_client.close()

注意:subprocess执行后的命令必须用gbk字符集

怎么样?你感觉这种解决方法牛逼不??
悄悄告诉你,low逼的很。
下面我就传授你们一个高逼格的玩法,让你们见识下什么叫做盖世神功。

高端玩法:
这里要用到struct库
思路(以ipconfig命令举例):
1.服务端先拿到客户端发来ipconfig执行结果的大小并赋给变量cmd_length(记得转成int类型)
2.服务端利用struct的pack方法把cmd_length(必须要是int类型)包装成一个4字节的内容并发送给客户端
3.服务端再发送真实的命令执行结果到客户端
4.客户端先收取4个字节,这样就拿到了ipconfig执行结果的大小(第2步已经把此结果封装到一个4字节的空间中了)
5.客户端拿到ipconfig执行结果的大小后(假设为2000字节),此时就可以按照上面low版的方法写个循环继续操作了。

服务端:
from socket import *
import subprocess
import struct
ip_port=('127.0.0.1',8003)
buffer_size=1024
backlog=5

tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(backlog)

while True:
    print('服务端开始运行...')
    conn,addr=tcp_server.accept()
    print('客户端已连接上来...')
    while True:
        try:
            cmd_msg=conn.recv(1024).decode('utf-8')
            if not cmd_msg:break
            res=subprocess.Popen(cmd_msg,shell=True,
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stdin=subprocess.PIPE)
            res_err=res.stderr.read()
            if res_err:
                res_cmd=res_err
            res_cmd=res.stdout.read()
            if not res_cmd:
                res_cmd='空命令已执行,只是没结果.'.encode('gbk')
            cmd_length=len(res_cmd)
            #int类型默认就是pack成4字节,得到的cmd_length是字节类型
            cmd_length=struct.pack('i',cmd_length)
            conn.send(cmd_length)
            conn.send(res_cmd)
        except Exception:
            break
    conn.close()
tcp_server.close()


客户端:
from socket import *
import struct
ip_port=('127.0.0.1',8003)
buffer_size=1024


tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    cmd_msg=input('输入命令:')
    if not cmd_msg:continue
    if cmd_msg == 'quit':break
    tcp_client.send(cmd_msg.encode('utf-8'))
    #收到的命令执行结果大小占多少字节
    cmd_length=tcp_client.recv(4)
    cmd_length=struct.unpack('i',cmd_length)
    client_cmd_length=0
    res_cmd=b''
    while client_cmd_length < cmd_length[0]:
        res_cmd += tcp_client.recv(1024)
        client_cmd_length = len(res_cmd)
    print(res_cmd.decode('gbk'))
tcp_client.close()

怎么样?惊不惊喜?意不意外?高不高端?
其实还有更高端的玩法,你想知道吗?对不起,确实有,但是我不会,哈哈哈哈哈哈哈。。。

标签:1024,--,res,cmd,TCP,粘包,length,client,tcp
From: https://www.cnblogs.com/kkbest/p/18371956

相关文章

  • 生成函数(GF)
    学了一点皮毛,暂时先写一篇博客寄存一下定义:比较抽象的理解一下就是把一个限制条件的方案数转化成一个次冥函数的形式,再把一个次幂函数转化成某种限制条件下的方案数.......大概是这么一个形式:\[f(x)=a_{0}x^0+a_{1}x^1+a_{2}x^2+·····\]还是举个例子吧:你现在要离校回家......
  • 05-02 Relationships Between Objects(对象之间的关系 )
    RelationshipsBetweenObjects(对象之间的关系)XPOsupportsthreetypesofrelationshipsbetweenobjects.Thetypeofarelationshipthatiscreateddependsuponhowrelatedobjectsaredefined.XPO支持对象之间的三种类型的关系。创建的关系类型取决于相关对......
  • S2 - Lesson 56 Faster than sound
     Content Howfastdidthewinningcargo?Onceayear,araceisheldforoldcars.Alotofcarsenteredforthisracelastyearandtherewasagreatdealofexcitementjustbeforeitbegan.OneofthemosthandsomecarswasaRolls-RoyceSilverGhost......
  • 2024.8.23 模拟赛总结
    A.distStatement:给定一棵\(n(n\le10^6)\)个节点带边权的树,定义\(\mathrm{Min}(x,y)\)是\((x,y)\)路径上的边权最小值。求\(\max_{r=1}^n{\sum_{v\nei}\mathrm{Min}(r,v)}\)。Solution:经典套路题。首先注意到一条路径上的只有最小值才会产生贡献,于是对于......
  • 05-03 Map Persistent Objects to Database Views(将持久对象映射到数据库视图 )
    MapPersistentObjectstoDatabaseViews(将持久对象映射到数据库视图)CreateaPersistentClass(创建持久类)Createapersistentclass.Theclassnameshouldmatchtheviewname.创建一个持久类。类名应与视图名匹配。AssignthePersistentattributetotheper......
  • 05-04 Basics of Creating Persistent Objects for Existing Data Tables(为现有数据表
    BasicsofCreatingPersistentObjectsforExistingDataTables(为现有数据表创建持久对象的基础知识)ToaccessanexistingdatatableinadatabaseandworkwithitusingthefunctionalityprovidedbyeXpressPersistentObjects(XPO),youneedtocreateap......
  • 除了按值和引用,方法参数的第三种传递方式
    参数在方法种具有按“值(byvalue)”和“引用(byref)”两种传递方式,这是每个.NET程序员深入骨髓得基本概念。但是我若告诉你,.NET规定的参数传递形式其实是三种,会不会颠覆你的认知。一、官方描述二、TypedReference结构体三、三个特殊的方法四、三种参数传递方式一、官方描述三种......
  • C语言-数组名与&数组名的细节注意
    这篇博客将通过整型数组、字符数组、字符串放在数组中、以及二维数组的数组名与&数组名和各类特殊情况的题目讲解来使得我们对于指针与数组名具有更加深刻的了解。注意:sizeof(数组名)-数组名表示整个数组的-计算的是整个数组的大小&数组名-数组名表示整个数组,取出的是整......
  • 这年头,好好分享前端高质量文章也要被怼
    起因大家好,我是欧阳。熟悉的同学都知道欧阳的源码文章大都是4000字(除去代码)以上的高质量文章,基本没有水文。但就是这样欧阳昨天也被某2.6kstar的开源大佬给怼了,被怼了后欧阳有点不舒服,就把被怼的截图(未打码)发到了粉丝群和朋友圈。过了一会儿才发现欧阳不仅被怼了,还在粉丝群......
  • 05-01 Create a Persistent Object(创建持久对象)
    CreateaPersistentObject(创建持久对象)TheXPOORMcanloadandsavetoadatastoreonlypersistentobjects.XPOORM只能加载持久对象并将其保存到数据存储中。Youmakeyourbusinessobjectspersistentinanyofthefollowingways:您可以通过以下任何方式......