从网络应用程序拆包操作类型的正确方式

Correct way of unpacking operation type from network application

本文关键字:类型 方式 操作 应用程序 网络 包操作      更新时间:2023-10-16

我来自python世界,作为一个周末项目,我决定用c++编写一个简单的UDP服务器。我有一个关于发现传入请求类型的正确方法的问题。我的方法是为每种可能的请求类型都创建一个类。当数据包到达时,我必须解压缩它的OPID(操作id)并实例化正确的类。要做到这一点,我必须将OPID与类绑定,而我在c++中熟悉的唯一方法是使用巨大的开关:case块。这样做对我来说并不太合适,而且如果我正确理解Uncleob,这就违背了一些OOP实践。由于代码描述了最好的意图,下面是我试图用c++做的python等价物。

class BaseOperation:
    OPID = 0
    def process(packet_data):
        raise NotImplementedError("blah blah")
class FooOperation(BaseOperation):
    OPID = 1
    def process(packet_data):
        print("Foo on the packet!")
class BarOperation(BaseOperation):
    OPID = 2
    def process(packet_data):
        print("Bar on the packet!")
opid_mappings = {
    FooOperation.OPID: FooOperation,
    BarOperation.OPID: BarOperation
}

在处理传入数据包的代码中的某个位置

def handle_connection(packet):
    try:
        operation = opid_mappings[get_opid(packet)]()
    except KeyError:
        print("Unknown OPID")
        return
    operation.process(get_data(packet))

真正快速破解基于对象的解决方案。在std::function的奇妙的新C++11世界中,这可能不是正确的做法。

如果BaseOperation的子级需要存储状态,请转到对象!

#include <iostream>
#include <map>
class BaseOperation
{
protected:
    int  OPID;
public:
    virtual ~BaseOperation()
    {
    }
    virtual int operator()() = 0;
};
class FooOperation:public BaseOperation
{
public:
    static constexpr int OPID = 1;
    FooOperation()
    {
    }
    int operator()()
    {
        // do parsing
        return OPID; // just for convenience so we can tell who was called
    }
};
constexpr int FooOperation::OPID; // allocate storage for static
class BarOperation:public BaseOperation
{
public:
    static constexpr int OPID = 2;
    BarOperation()
    {
    }
    int operator()()
    {
        // do parsing
        return OPID; // just for convenience so we can tell who was called
    }
};
constexpr int BarOperation::OPID; // allocate storage for static
std::map<int, BaseOperation*> opid_mappings{
    {FooOperation::OPID, new FooOperation()},
    {BarOperation::OPID, new BarOperation()}
};
int main()
{
    std::cout << "calling OPID 1:" << (*opid_mappings[1])() << std::endl;
    std::cout << "calling OPID 2:" << (*opid_mappings[2])() << std::endl;
    for (std::pair<int, BaseOperation*> todel: opid_mappings)
    {
        delete todel.second;
    }
    return 0;
}

这也忽略了一个事实,即可能不需要地图。如果OPID是连续的,那么一个好的ol’dumb阵列就能解决问题。我喜欢这个映射,因为如果有人移动一个解析器处理程序或在列表中间插入一个,它就不会出错。

无论如何,这会带来一堆内存管理问题,比如for循环需要删除main底部的解析器对象。这可以用std::unique_ptr解决,但这可能是一个我们不需要去的兔子洞。

解析器没有任何状态的可能性非常大,我们可以只使用OPID和std::function的映射。

#include <iostream>
#include <map>
#include <functional>
static constexpr int FooOPID = 1;
int fooOperation()
{
    // do parsing
    return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation()
{
    // do parsing
    return BarOPID;
}
std::map<int, std::function<int()>> opid_mappings {
    {FooOPID, fooOperation},
    {BarOPID, BarOperation}
};
int main()
{
    std::cout << "calling OPID 1:" << opid_mappings[1]() << std::endl;
    std::cout << "calling OPID 2:" << opid_mappings[2]() << std::endl;
    return 0;
}

因为如果你不传递任何东西,解析器就没用了,最后一个调整是:

#include <iostream>
#include <map>
#include <functional>
struct Packet
{
    //whatever you need here. Probably a buffer reference and a length
};
static constexpr int FooOPID = 1;
int fooOperation(Packet & packet)
{
    // do parsing
    return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation(Packet & packet)
{
    // do parsing
    return BarOPID;
}
std::map<int, std::function<int(Packet &)>> opid_mappings {
    {FooOPID, fooOperation},
    {BarOPID, BarOperation}
};
int main()
{
    Packet packet;
    std::cout << "calling OPID 1:" << opid_mappings[1](packet) << std::endl;
    std::cout << "calling OPID 2:" << opid_mappings[2](packet) << std::endl;
    return 0;
}