maxminddb地理信息库–C语言

maxminddb地理信息库–C语言

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 不是线程安全的,如果在并发中使用,注意临界资源抢夺。

编译测试

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注