maxminddb 是一个 IP 的地理信息库,可以根据 IP 地址给出对应的地理位置信息。
下载离线库
maxminddb提供在线查询,也提供了离线库。
离线库下载方法可从这个地址找一下:https://support.maxmind.com/hc/en-us/articles/4408216129947-Download-and-Update-Databases
下载需要注册账号并登录,注册还是挺方便的,只需要有个邮箱就可以了。
这里我下载了两个库:GeoLite2-Country 和 GeoLite2-City,一个精确到国家或地区,一个精确到城市。这个是两个压缩包,解压后内容如下:

LIB库
在C语言中使用此离线库,需要用到的接口在 libmaxminddb 库中,代码是开源的。
代码地址:https://github.com/maxmind/libmaxminddb
可以编译安装。
另外,大多数系统库里也有 libmaxminddb 库,也可以直接从系统源里安装。

代码
上述两个步骤都完成了,就可以在C语言中使用了,下面是给的一个示例。
// test.c
// gcc -Wall -o test test.c -lmaxminddb
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// MaxMindDB的API函数头文件
#include <maxminddb.h>
/**
* @brief 获取 MMDB_s 句柄
*
* @param db_path 离线信息库地址
* @param mmdb MMDB_s 句柄
*
* @return 0 - success -1 - failed
*/
int MMDB_init( const char *db_path, MMDB_s * mmdb ) {
int stat = MMDB_open(db_path, MMDB_MODE_MMAP, mmdb);
if (stat != MMDB_SUCCESS) {
printf("Failed to open database: %s\n", db_path);
return -1;
}
return 0 ;
}
#define COUNTRY_CODE_LEN 16
#define PROVINCE_LEN 64
#define CITY_LEN 64
#define TIMEZONE_LEN 32
#define COMPANY_LEN 256
/**
* @brief 从 maxmmind 库里获取的 地理位置信息
*/
typedef struct GEO_info_ {
char country_code[COUNTRY_CODE_LEN] ;
char province[PROVINCE_LEN] ;
char city[CITY_LEN] ;
char timezone[TIMEZONE_LEN] ;
uint32_t asn_num ;
char company[COMPANY_LEN] ;
} GEO_info ;
#define NA "N/A"
/**
* @brief 给出默认状态的 GEO_info 值
*
* @param geo_rslt 需要被修改的结构指针
*/
void set_default_geo_info(GEO_info * geo_info) {
strncpy( geo_info->country_code, NA, COUNTRY_CODE_LEN ) ;
strncpy( geo_info->province, NA, PROVINCE_LEN ) ;
strncpy( geo_info->city, NA, CITY_LEN ) ;
strncpy( geo_info->timezone, NA, TIMEZONE_LEN ) ;
geo_info->asn_num=0 ;
strncpy( geo_info->company, NA, COMPANY_LEN ) ;
}
/**
* @brief 打印 GEO_info 结构里的数据
*
* @param geo
*/
void print_geo_info( GEO_info * geo) {
printf("Geo信息:\n");
printf(" CountryOrRegin: [%s]\n", geo->country_code);
printf(" Province: [%s]\n", geo->province);
printf(" City: [%s]\n", geo->city);
printf(" Timezone: [%s]\n", geo->timezone);
printf(" ASN: [%u]\n", geo->asn_num);
printf(" Company: [%s]\n", geo->company);
}
/**
* @brief 解析字符串类型的结果给 GEO_info 赋值
*
* @param entry_data 解析的结果指针
* @param rslt 结果保存地址
* @param rslt_len 最大使用长度
*/
void set_one_data_str( MMDB_entry_data_s * entry_data, char * rslt, int rslt_len ) {
if( entry_data->has_data && entry_data->type == MMDB_DATA_TYPE_UTF8_STRING ) {
memcpy( rslt, entry_data->utf8_string,
strlen(entry_data->utf8_string) > rslt_len-1 ? rslt_len-1 : strlen(entry_data->utf8_string) ) ;
} else {
memcpy( rslt, NA,
strlen(NA) >rslt_len-1 ? rslt_len-1 : strlen(NA) ) ;
}
}
/**
* @brief 解析int类型的结果给 GEO_info 赋值
*
* @param entry_data 解析的结果指针
* @param rslt 结果保存地址
*/
void set_one_data_int( MMDB_entry_data_s * entry_data, uint32_t * rslt ) {
if( entry_data->has_data && entry_data->type == MMDB_DATA_TYPE_UINT32 ) {
* rslt = entry_data->uint32 ;
} else {
* rslt = 0 ;
}
}
/**
* @brief 根据 IP 地址,获取地理位置信息
*
* @param ip_str IP 地址,字符串形式的结构,点分结构
* @param mmdb 初始化的地理信息查询库
* @param geo_rslt 存放查询的结构
*
* @return 0-success -1-failed
*/
int get_location_by_ip( char * ip_str, MMDB_s * mmdb, GEO_info * geo_rslt ) {
if( ip_str == NULL || strlen(ip_str) == 0 || mmdb == NULL || geo_rslt == NULL ) {
printf("param error!\n") ;
return -1 ;
}
int gai_error, mmdb_error ;
MMDB_lookup_result_s result = MMDB_lookup_string(mmdb, ip_str, &gai_error, &mmdb_error) ;
if( 0 != gai_error || MMDB_SUCCESS != mmdb_error ) {
printf("Error from MMDB_lookup_string\n") ;
set_default_geo_info(geo_rslt);
return -1 ;
}
MMDB_entry_data_s country_iso_code, subdivision_name, city_name, timezone, asn, organization;
// 获取国家代码
MMDB_get_value(&result.entry, &country_iso_code, "country", "iso_code", NULL);
set_one_data_str( &country_iso_code, geo_rslt->country_code, sizeof(geo_rslt->country_code) ) ;
// 获取省份(一级行政区划)
MMDB_get_value(&result.entry, &subdivision_name, "most_specific_subdivision", "names", "en", NULL);
MMDB_get_value(&result.entry, &subdivision_name, "subdivisions", "0", "names", "en", NULL);
set_one_data_str( &subdivision_name, geo_rslt->province, sizeof(geo_rslt->province) ) ;
// 获取城市名称
MMDB_get_value(&result.entry, &city_name, "city", "names", "en", NULL);
set_one_data_str( &city_name, geo_rslt->city, sizeof(geo_rslt->city) ) ;
// 获取时区
MMDB_get_value(&result.entry, &timezone, "location", "time_zone", NULL);
set_one_data_str( &timezone, geo_rslt->timezone, sizeof(geo_rslt->timezone) ) ;
// 获取 ASN 和公司信息
MMDB_get_value(&result.entry, &asn, "traits", "autonomous_system_number", NULL);
set_one_data_int(&asn, &(geo_rslt->asn_num));
MMDB_get_value(&result.entry, &organization, "traits", "organization", NULL);
set_one_data_str( &organization, geo_rslt->company, sizeof(geo_rslt->company) ) ;
return 0 ;
}
int main(int argc, char *argv[]) {
// 选项处理
if (argc != 3) {
printf("Usage: %s <database_path> <ip_address>\n", argv[0]);
return -1;
}
MMDB_s mmdb ;
MMDB_init( argv[1], &mmdb ) ;
GEO_info geo_rslt ;
memset( &geo_rslt, 0x00, sizeof(geo_rslt) ) ;
get_location_by_ip( argv[2], &mmdb, &geo_rslt ) ;
print_geo_info( &geo_rslt) ;
return 0;
}
注意:上述代码中,使用 MMDB_open 获取的句柄 MMDB_s * mmdb 不是线程安全的,如果在并发中使用,注意临界资源抢夺。
编译测试

发表回复