c++ IP地址离线查询

在编写服务器程序的时候,我们可能会对连接的客户端IP进行一些过滤、统计,...等等的一些操作。在此就需要一些技术手段来分析客户端的IP地址。总的来说可以有以下几种方法。

1、通过在线ip服务(使用post/get等技术手段)

投入少,但是查询速度慢,效率不高,对于并发量大的情况,效果不好。

2、通过离线ip查询

(1)使用付费版,投入大,效率高。

(2)使用免费版,限制大。

(3)使用开源版,效率高,但是准确率稍差。

结合上述几点看法,最终我选择开源离线IP查询方案。

就我本人认知来说,主要有两种方案:

1、选择纯真ip查询

纯真ip查询的数据库,是GBK编码,在utf8环境下会产生乱码,因此需要对纯真数据库进行转换,稍显麻烦,下面我会给出转换工具。

https://download.csdn.net/download/a13576560181/16231956

包装类代码

/**
 * 纯真离线IP查询
 */
class CZ_IpQuery {
public:
    /**
     * 把整个文件读到一个字符串中
     * @param _file
     */
    inline  explicit CZ_IpQuery(std::string _file) {
        std::ifstream ifs(_file, std::ios::binary);
        if (ifs.is_open()) {
            state = true;
            ifs.seekg(0, std::ios::end);
            size_t _fsz = (size_t) ifs.tellg();
            ifs.seekg(0, std::ios::beg);
            m_bytes.resize(_fsz);
            ifs.read(&m_bytes[0], (std::streamsize) _fsz);
            ifs.close();
            getHead();
        }

    }


    /**
     * 返回初始化状态
     * @return
     */
    inline operator bool() {
        return state;
    }

    /**
     * 返回查询结果
     * @param _ip
     * @return
     */
    inline std::tuple<bool, std::string, std::string> operator()(std::string &&_ip) {
        auto ip = getIP(std::move(_ip));
        if (ip != 0) {
            auto current = searchIP(m_index_head, m_index_tail, ip);
            auto[country, location] = getAddress(getValue(current + 4, 3));
            return std::make_tuple(true, country, location);
        } else {
            return std::make_tuple(false, "", "");
        }
    }


private:
    /**
     * 获取指定的16进制串的值
     * @param start
     * @param length
     * @return
     */
    inline unsigned long getValue(unsigned long start, int length) {
        unsigned long variable = 0;
        long val[length], i;
        for (i = 0; i < length; i++) {
            /*过滤高位,一次读取一个字符*/
            val[i] = m_bytes[start++] & 0x000000FF;
        }
        for (i = length - 1; i >= 0; i--) {
            /*因为读取多个16进制字符,叠加*/
            variable = variable * 0x100 + val[i];
        }
        return variable;
    }


    /**
     * 获取指定的字符串
     * @param start
     * @return
     */
    inline std::string getString(unsigned long start) {
        char val;
        std::string string;
        /*读取字符串,直到遇到0x00为止*/
        do {
            val = m_bytes[start++];
            /*依次放入用来存储的字符串空间中*/
            string += val;
        } while (val != 0x00);
        return string;
    };


    /**
     * 读取指定IP的国家位置和地域位置
     * @param start
     * @return
     */
    inline std::tuple<std::string, std::string> getAddress(unsigned long start) {
        unsigned long redirect_address, counrty_address, location_address;
        char val;
        std::string country, location;
        start += 4;
        /*读取首地址的值*/
        val = (m_bytes[start] & 0x000000FF);

        if (val == REDIRECT_MODE_1) {
            /*重定向1类型的*/
            redirect_address = getValue(start + 1, 3);

            /*混合类型,重定向1类型进入后遇到重定向2类型
                  读取重定向后的内容,并设置地域位置的文件偏移量*/
            if ((m_bytes[redirect_address] & 0x000000FF) == REDIRECT_MODE_2) {
                counrty_address = getValue(redirect_address + 1, 3);
                location_address = redirect_address + 4;
                country = getString(counrty_address);
            }
                /*读取重定向1后的内容,并设置地域位置的文件偏移量*/
            else {
                counrty_address = redirect_address;
                country = getString(counrty_address);
                location_address = redirect_address + country.length();
            }
        }
            /*重定向2类型的*/
        else if (val == REDIRECT_MODE_2) {
            counrty_address = getValue(start + 1, 3);
            location_address = start + 4;
            country = getString(counrty_address);
        } else {
            counrty_address = start;
            country = getString(counrty_address);
            location_address = counrty_address + country.length();
        }

        /*读取地域位置*/

        if ((m_bytes[location_address] & 0x000000FF) == REDIRECT_MODE_2 ||
            (m_bytes[location_address + 1] & 0x000000FF) == REDIRECT_MODE_1) {
            location_address = getValue(location_address + 1, 3);
        }
        location = getString(location_address);

        return std::make_tuple(country, location);
    };


    /**
     * 读取索引部分的范围(在文件头中,最先的2个8位16进制)
     */
    inline void getHead() {
        /*索引的起止位置的文件偏移量,存储在文件头中的前8个16进制中
          设置偏移量为0,读取4个字符*/
        m_index_head = getValue(0L, 4);
        /*索引的结束位置的文件偏移量,存储在文件头中的第8个到第15个的16进制中
          设置偏移量为4个字符,再读取4个字符*/
        m_index_tail = getValue(4L, 4);
    }


    /**
     * 搜索指定IP在索引区的位置,采用二分查找法;
       返回IP在索引区域的文件偏移量
       一条索引记录的结果是,前4个16进制表示起始IP地址
       后面3个16进制,表示该起始IP在IP信息段中的位置,文件偏移量
     * @param index_start
     * @param index_end
     * @param ip
     * @return
     */
    inline unsigned long searchIP(unsigned long index_start,

                                  unsigned long index_end, unsigned long ip) {
        unsigned long index_current, index_top, index_bottom;
        unsigned long record;
        index_bottom = index_start;
        index_top = index_end;
        /*此处的7,是因为一条索引记录的长度是7*/
        index_current = ((index_top - index_bottom) / 7 / 2) * 7 + index_bottom;
        /*二分查找法*/
        do {
            record = getValue(index_current, 4);
            if (record > ip) {
                index_top = index_current;
                index_current = ((index_top - index_bottom) / 14) * 7 + index_bottom;
            } else {
                index_bottom = index_current;
                index_current = ((index_top - index_bottom) / 14) * 7 + index_bottom;
            }
        } while (index_bottom < index_current);
        /*返回关键字IP在索引区域的文件偏移量*/
        return index_current;
    }


    /**
     * 判断一个字符是否为数字字符,如果是,返回0 如果不是,返回1
     * @param c
     * @return
     */
    inline bool beNumber(char c) {
        return (c >= '0' && c <= '9');

    }

    /**
     * 函数的参数是一个存储着IP地址的字符串首地址  返回该IP的16进制代码  如果输入的IP地址有错误,函数将返回0
     * @param ip_addr
     * @return
     */
    inline unsigned long getIP(std::string &&ip_addr) {
        unsigned long ip = 0;
        int i, j = 0;
        /*依次读取字符串中的各个字符*/
        for (i = 0; i < ip_addr.length(); i++) {
            /*如果是IP地址间隔的‘.’符号
                  把当前读取到的IP字段的值,存入ip变量中
                  (注意,ip为叠加时,乘以16进制的0x100)
                  并清除临时变量的值*/
            if (ip_addr[i] == '.') {
                ip = ip * 0x100 + j;
                j = 0;
            }
                /*往临时变量中写入当前读取到的IP字段中的字符值
                      叠加乘以10,因为输入的IP地址是10进制*/
            else {
                /*判断,如果输入的IP地址不规范,不是10进制字符
                          函数将返回0*/
                if (beNumber(ip_addr[i]))
                    j = j * 10 + ip_addr[i] - '0';
                else
                    return 0;
            }
        }
        /*IP字段有4个,但是‘.’只有3个,叠加第四个字段值*/
        ip = ip * 0x100 + j;
        return ip;
    }

private:
    bool state{false};
    std::string m_bytes;
    uint32_t m_index_head = 0;
    uint32_t m_index_tail = 0;
};

 使用方法

CZ_IpQuery ipQuery("数据库路径");
auto[status, country, location] = ipQuery("待查询IP");
if (status) {
    ;;;;
}

2、选择ip2region

项目地址:https://github.com/lionsoul2014/ip2region

下载项目后,需要对c绑定源代码进行下修改,在ip2region.h的头尾分别加上

#if defined(__cplusplus)
extern "C" {
#endif
#if defined(__cplusplus)
}
#endif

 原因是,ip2region 的c绑定,是纯C语言写的,直接用c++调用会产生问题。

然后将 ip2region.h 和 ip2region.c添加到工程内。

包装类代码

/**
 * ip2region离线IP查询
 */
class IP2_IpQuery {
public:

    using SearchFunc =  uint_t (*)(ip2region_t, const char *, datablock_t);

    enum class SearchType {
        Binary,
        Memory
    };

    explicit IP2_IpQuery(std::string &&dbFile, SearchType searchType = SearchType::Memory) {
        if (ip2region_create(&ip2rEntry, dbFile.c_str())) {
            switch (searchType) {
                case SearchType::Binary:
                    func_ptr = ip2region_binary_search_string;
                    status = true;
                    return;
                case SearchType::Memory:
                    func_ptr = ip2region_memory_search_string;
                    status = true;
                    return;
                default:
                    func_ptr = nullptr;
                    status = false;
                    return;
            }
        }

        status = false;

    }

    ~IP2_IpQuery(){
        ip2region_destroy(&ip2rEntry);
    }


    inline operator bool (){
        return status;
    }

    inline std::tuple<bool, int, std::string> operator() (std::string &&ip) {
        datablock_entry datablock;
        if (func_ptr != nullptr) {
            func_ptr(&ip2rEntry, ip.c_str(), &datablock);
            return std::make_tuple(true, datablock.city_id, datablock.region);
        } else {
            return std::make_tuple(false, -1, "");
        }
    }


private:
    ip2region_entry ip2rEntry;
    SearchFunc func_ptr;
    bool status = false;
};

使用方法 

IP2_IpQuery ipQuery("数据库路径");
auto[status, city_id, region] = ipQuery("待查询IP");
if (status) {
    ;;;;
}

以上代码采用 c++17标准编写。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值