理解策略路由

一、linux内置的路由表

linux默认三种路由表,存放在/etc/iproute2/rt_tables

local:  本地接口地址,广播地址,以及NAT地址都放在这个表。该路由表由系统自动维护,管理员不能直接修改。
main:  执行 route -n就是读取这张表的信息。如果没有指明路由所属的表,所有的路由都默认放在这个表里。
default: 默认的路由。

[root@f8s home]# ip rule show
0:      from all lookup local 
32766:  from all lookup main 
32767:  from all lookup default

二、规则和路由的关系

规则(ip rule)控制使用那个路由表,ip table往路由表中设置路由信息

三、设置策略路由

结合例子说明设置策略路由的过程

1、创建一个虚拟网卡eth2.300

# 创建虚拟网卡
vconfig add eth2 300
# 设置虚拟网卡的IP地址,并启用网卡
ifconfig eth2.300 192.168.100.50 netmask 255.255.255.0 up

通过命令ifconfig查看网卡信息

[root@f8s home]# ifconfig 
eth2      Link encap:Ethernet  HWaddr 00:0C:29:87:80:CD  
          inet addr:192.168.255.128  Bcast:192.168.255.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe87:80cd/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:90005 errors:0 dropped:0 overruns:0 frame:0
          TX packets:87454 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:15158203 (14.4 MiB)  TX bytes:13314917 (12.6 MiB)
          Interrupt:18 Base address:0x2000 
 
eth2.300  Link encap:Ethernet  HWaddr 00:0C:29:87:80:CD  
          inet addr:192.168.100.50  Bcast:192.168.100.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe87:80cd/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:27 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 

2、创建一条规则, 从地址192.168.100.50发送的数据,都走路由表100

# 创建路由规则
ip rule add from 192.168.100.50/32 table 100

查看规则信息:

[root@f8s home]# ip rule show
0:      from all lookup local 
32765:  from 192.168.100.50 lookup 100 
32766:  from all lookup main 
32767:  from all lookup default 

3、往路由表100,添加路由信息

# 发到192.168.100.0/24网段的网络包,从网卡eth2.300发生出去,包的源IP地址设为192.168.100.50
ip route add 192.168.100.0/24 dev eth2.300 src 192.168.100.50 table 100

路由表table 100的路由信息:

[root@f8s home]# ip route show table 100
192.168.100.0/24 dev eth2.300  scope link  src 192.168.100.50

四、总结

策略路由:首先通过ip rule定义规则,确认使用那个路由表,然后通过ip route往路由表中添加路由信息。

 

 

 

 

 

自制智能指针

一、智能指针的原理

智能指针就是封装了创建对象的指针,并且可以在对象过期的时候,让析构函数自动删除指向的内存。

二、代码实现

为了防止拷贝和赋值,我们将拷贝函数和赋值构造函数放到private私有区域内。智能指针的类模版的实现方式:

template <typename T>
class SmartPtr
{
public:
    // explicit防止隐式转换
    explicit SmartPtr(T *pObj);
    ~SmartPtr(void);

public:
    T* operator->();
    T* Get();
    void Reset(T* pObj);
    
private:
    // 防止拷贝和赋值
    SmartPtr(const SmartPtr &);
    SmartPtr & operator= (const SmartPtr &);
    
    T* m_pObj;
};

template<typename T>
SmartPtr<T>::SmartPtr(T *pObj):m_pObj(pObj)
{
    
}

template<typename T>
SmartPtr<T>::~SmartPtr(void)
{
    delete m_pObj;
    m_pObj = 0;
}

template<typename T>
T* SmartPtr<T>::operator->()
{
    return m_pObj;
}

template<typename T>
T* SmartPtr<T>::Get()
{
    return m_pObj;
}

template<typename T>
void SmartPtr<T>::Reset(T* pObj)
{
    delete m_pObj;
    m_pObj = m_pObj;
}

三、测试验证

为了验证智能指针在退出的时候是否会自动调用delete来释放动态创建的对象,我们定义实现ObjectA对象。具体的实现细节如下所示:

class ObjectA
{
public:
    // 构造函数
    ObjectA(int iNum);
    ObjectA();
    // 析构函数
    ~ObjectA();
 
private:
    int m_num;
};
ObjectA::ObjectA(int iNum):m_num(iNum)
{
    cout << "ObjectA constructor" << endl;
}

ObjectA::ObjectA()
{
    cout << "ObjectA constructor, no param" << endl;
}

ObjectA::~ObjectA()
{
    cout << "ObjectA destructor" << endl;
}

完成上面ObjectA对象的定义和实现后,接下来就开始进行验证,验证代码如下:

ObjectA *pobj = new ObjectA;
SmartPtr<ObjectA> smartPtr(pobj);

通过调试运行,最后输出的结果为:

ObjectA constructor, no param
ObjectA destructor

这说明智能指针调用自身的析构函数,而析构函数中又回去删除动态创建对象的内存。

四、注意事项

因为模版不是函数,不能单独编译,所以实现智能指针的模版类的时候,需要将所有模版信息放入头文件中,其他文件使用的时候,再包含该文件。

五、总结

智能指针的机制就是利用本地变量在退出函数之后会自动删除的特性。首先保存动态创建对象的指针,当本地变量的生命周期结束的时候,自动调用其析构函数,而析构函数则会释放保存的指针指向的内存,从而达到避免调用new而忘记delete导致出现内存泄露的问题。

设计模式详解二:原型模式(Prototype)

原型模式,简单说,就是自我复制的功能,通过已存在对象来创建新的对象。本文将通过C++的拷贝构造函数来实现原型模式。

一、Prototype模式的结构图

Prototype模式的结构图很简单,定义基类Prototype,然后声明创建新对象的接口Clone,  继承自基类Prototype的子类SubPrototype, 该类实现接口Clone,并且实现拷贝构造函数。

二、程序代码实现

Prototype模式的代码实现也比较简单,下面直接给出代码

1. 首先类的声明

class Prototype
{
public:
    // 析构函数
    virtual ~Prototype();
    // 纯虚函数
    virtual Prototype *Clone() const = 0;
    
protected:
    // 构造函数,设置为protected, 防止创建实例对象
    Prototype();
};

class SubPrototype:public Prototype
{
public:
    // 构造函数
    SubPrototype();
    // 析构函数
    ~SubPrototype();
    // 拷贝构造函数
    SubPrototype(const SubPrototype &sp);
    // 复制对象
    Prototype *Clone() const;
};

2. 接着是类的实现

Prototype::Prototype()
{
    
}

Prototype::~Prototype()
{
    
}



SubPrototype::SubPrototype()
{
    
}

SubPrototype::~SubPrototype()
{
    
}

SubPrototype::SubPrototype(const SubPrototype &sp)
{
    DEBUG_LOG("", "copy constructor");
}

Prototype *SubPrototype::Clone() const
{
    // 这里调用到拷贝构造函数
    return new SubPrototype(*this);
}

三、测试验证结果

完成代码实现之后,我们在主程序代码上输入如下代码段来确认是否调用到拷贝构造函数,是否符合我们的预期设想。

Prototype * pobj1 = new SubPrototype();

Prototype * pobj2 = pobj1->Clone();

运行结果如下所示:

[2019-04-13 14:13:37.248  SubPrototype:36]   = copy constructor

从运行结果看,调用Clone函数之后,成功调用到拷贝构造函数,这里的拷贝构造函数内部只是打印了日志信息来方便我们的验证,实际开发过程中,实现拷贝构造函数要特别注意深拷贝和浅拷贝的问题。

四、总结

Prototype模式就是复制自己的信息来创建新的对象,是属于创建型的模式。

字符串string操作效率的思考

平常开发的时候,我们使用字符串string进行各种操作的过程中,有时候为了清晰的逻辑或者操作的简便,经常喜欢将字符串拷贝到另一个字符串中,再进行各种操作,在数据量少或者操作次数比较少的情况下,一般对程序的效率是不会有太大的影响,但是当数数据量大或者操作次数大的情况下,情况会是怎样呢?话不多说,首先,给出两个分隔字符串的函数,一个是没有优化的,其中采用字符串的赋值,另一个经过了优化。

1> 没有优化的分隔字符串函数

// 按照指定的分隔符,分隔字符串
// 例如split_string_test("a|b|c","|", vec);
// 得到的结果vec = ["a","b","c"]
void Public::split_string_test(const string &src, const string &split, vector<string> &vec_ret)
{
    vec_ret.clear();
    string tmp = src;
    
    while(1)
    {
        // 注意:size_t是unsigned, 而ssize_t是有符号整型,这里必须使用ssize_t
        // 32位系统上 size_被定义为 typedef unsigned innt size_t;  ssize_t等价于int
        ssize_t iPos = tmp.find(split);
        if (iPos <= 0)
        {
            vec_ret.push_back(tmp);
            break;
        }
        vec_ret.push_back(tmp.substr(0,iPos));
        tmp = tmp.substr(iPos + split.size());
    }
}

2> 经过优化的分隔字符串函数

// 按照指定的分隔符,分隔字符串
// 例如split_string("a|b|c","|", vec);
// 得到的结果vec = ["a","b","c"]
void Public::split_string(const string &src, const string &split, vector<string> &vec_ret)
{
    vec_ret.clear();
    const string &tmp = src;
    ssize_t iPos_l = 0;
    ssize_t iPos_r = 0;
    while(1)
    {
        // 从iPos_l位置开始查找分隔符split
        iPos_r = tmp.find(split,iPos_l);
        if (iPos_r <= 0)
        {
            vec_ret.push_back(tmp.substr(iPos_l));
            break;
        }
        vec_ret.push_back(tmp.substr(iPos_l,iPos_r - iPos_l));
        iPos_l = iPos_r + split.size();
    }
}

从上面两个分隔字符串函数中,可以看出第一个分隔字符串函数将传入的源字符串拷贝到临时的变量中进行操作,而第二个分隔字符串函数通过引用和位置的索引的方式来操作源字符串。接下来,我们通过实验来看看两者之间在效率上有多大差异。

主函数输入如下所示的代码段来进行实验

int i = 0;
string src = "a|b|c";
string split = "|";
vector<string> vec_ret;
unsigned long long ull_start_time;
unsigned long long ull_end_time;
int icount = 100;

// 没有优化的函数
ull_start_time = Public::get_system_time();
for (i = 0; i < icount; i++)
{
    Public::split_string_test(src, split, vec_ret);
}
ull_end_time = Public::get_system_time();
cout << "[split_string_test] time: " << ull_end_time - ull_start_time << " ms" <<endl;
   
// 经过优化的函数
ull_start_time = Public::get_system_time();
for (i = 0; i < icount; i++)
{
    Public::split_string(src, split, vec_ret);
}
ull_end_time = Public::get_system_time();
cout << "[split_string] time: " << ull_end_time - ull_start_time << " ms" <<endl;

一、 正常情况

字符串src = “a|b|c”;设置操作的次数为icount = 100; 运行调试结果如下所示

[split_string_test] time: 0 ms
[split_string] time: 0 ms

从上面运行的结果看,两个函数基本上没有耗时,看不出任何差异

二、 操作次数增加的情况

字符串src = “a|b|c”;设置操作的次数为icount = 10000; 运行调试结果如下所示

[split_string_test] time: 10 ms
[split_string] time: 0 ms

从上面运行的结果看,操作次数增加的情况下,第一个函数比第二个函数多耗时10ms

三、数据量增加的情况

字符串src = “a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j”;设置操作的次数为icount = 100; 运行调试结果如下所示

[split_string_test] time: 20 ms
[split_string] time: 10 ms

从上面运行的结果看,数据量增加的情况下,第一个函数比第二个函数多耗时10ms

四、数据量增加,操作次数增加情况

字符串src = “a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j|a|b|c|d|e|f|g|h|i|j”;设置操作的次数为icount = 10000; 运行调试结果如下所示

[split_string_test] time: 1710 ms
[split_string] time: 600 ms

从上面运行的结果看,数据量增加,操作次数增加的情况下,第一个函数比第二个函数竟然多耗时1.11s, 这可是相当可观的数据。

五、总结

从上面实验结果看,字符串的赋值拷贝操作,数据量大、操作次数多的情况下,能够明显看出比较耗时的。因此,我们在进行字符串的操作的时候,尽量采用引用和指针的方式来提高效率。