在编写服务器程序的时候,我们可能会对连接的客户端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标准编写。