GeoIP的使用-C语言版
0x00. 简介
GeoIP库可以根据IP地址(支持IPv4 和 IPv6), 定位该IP所在的 洲、经纬度、国家、省市、ASN 等信息。
GeoIP目前已经升级到GeoIP2,GeoIP2有两个版本,一个免费版(GeoLite2),一个收费版本(GeoIP2, 200$起步)。
收费版本的准确率稍高一些,更新频率为每周二更新一次, 免费版是每月第一个周二更新一次。
两者对比可以参考官网说明 https://www.maxmind.com/en/geoip2-city-accuracy-comparison
对于大部分项目来说免费版已经足够使用了.
除了GeoIP外, 其实还有 ip2location、Quova等也提供类似的功能, 但都是收费的.
0x01. 资源下载
很多linux版本支持这个库, 可以使用yum 或 apt 进行下载, windows上使用的话就需要自己编译了.
源码下载:
https://dev.maxmind.com/geoip/geoip2/downloadable/
GeoIP2提供了多种语言的API接口供选择.
这里我需要使用C语言接口, 所以下载C语言版的源码.
https://github.com/maxmind/libmaxminddb/releases
GeoIP数据库下载:
https://dev.maxmind.com/geoip/geoip2/geolite2/

可以看到官网提供三种库,2种格式, 首先 官网API是需要使用二进制库文件, CSV格式的库可以导入其他程序 或 供你简单浏览。
三种库的区别可以从名字上就可以看出来:
City 精确到城市(大小70M左右),
Country 精确到国家(4M左右),
ASN 用于产看IP地址的拥有者(7M左右). 需要注意的是 City 和 Country 库中不含ASN信息。
对于ASN的理解可以通过知乎了解一下 https://www.zhihu.com/question/21024981
根据业务需求选择. 这里我们下载精确到城市的数据库文件.
https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz
由于数据库经常更新, 官网还提供了更新的方案:
https://dev.maxmind.com/geoip/geoipupdate/#For_Free_GeoLite2_Databases
0x02.接口说明
http://maxmind.github.io/libmaxminddb/
/* ------------------数据库的关闭与打开
* 这里 MMDB_open 的 flags 参数需要说明一下,
* 目前代码实现db文件都是使用mmap()映射到内存的,
* 所以flags其实设置什么都无所谓,所以默认使用 MMDB_MODE_MMAP就好.
*/
int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb);
void MMDB_close(MMDB_s *const mmdb); /* ------------------数据搜索
* 网络字节序地址搜索API, 即传入sockaddr参数是网络字节序的地址 */
MMDB_lookup_result_s MMDB_lookup_sockaddr(MMDB_s *const mmdb,
const struct sockaddr *const sockaddr, int *const mmdb_error); /* 字符串IP地址搜索API, 即传入ipstr参数的null结尾字符串,如"114.240.52.162"
* 这个API其实就是调用 getaddrinfo() 将字符串转换为网络地址,
* 然后再调用 MMDB_lookup_sockaddr() 实现的,
* 所以不要将网络地址转成字符串,再调用这个函数, 直接使用MMDB_lookup_sockaddr()
* gai_error参数 是用来返回 getaddrinfo() 错误码的.
*/
MMDB_lookup_result_s MMDB_lookup_string(MMDB_s *const mmdb, const char *const ipstr,
int *const gai_error, int *const mmdb_error); /* ------------------从搜索到数据中提取指定数据
* 下面 3 个函数意义相同, 只不过传入参数的方法不同.
* 3个函数前2个参数相同, 第一个是 搜索结果中搜到的entry,具体看例子程序
* 第二个参数是 获取数据的存放处, 剩下的参数都是搜索用的数据.
* 其实这3个函数实现是层层调用的关系:
* MMDB_get_value()是可变参数函数, 函数内部把 可变参数 用 va_list 封装起来,
* 接着调用 MMDB_vget_value(), 这个 vget函数把所有可变参数 再分离开来,
* 放到一个 字符串指针数组里面, 再把这个数组传递给 MMDB_aget_value()函数,
* 最终就是 aget 函数完成搜索功能.
* 所以如果考虑到性能, 那个最好直接调用 aget 函数.
* 另外, 需要注意 传入的参数 最后一个必须是 NULL, 否则会导致程序崩溃
*/
int MMDB_get_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, ...);
int MMDB_vget_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, va_list va_path);
int MMDB_aget_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, const char *const *const path); /* ------------------从搜索到数据中提取全部数据
* 调用完MMDB_lookup_sockaddr/MMDB_lookup_string 后获取所有该IP地址相关信息
* 具体使用看官方例子, 用处不大, 主要是可以用来了解一下结果数据的组成
*/
int MMDB_get_entry_data_list(MMDB_entry_s *start, MMDB_entry_data_list_s **const entry_data_list);
int MMDB_dump_entry_data_list(FILE *const stream,MMDB_entry_data_list_s *const entry_data_list, int indent);
void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list);
int MMDB_get_metadata_as_entry_data_list(MMDB_s *const mmdb, MMDB_entry_data_list_s **const entry_data_list); /* ------------------其他, MMDB_read_node例子可以看源码的 read_node_t.c,个人觉得没什么用 */
const char *MMDB_strerror(int error_code);
const char *MMDB_lib_version(void);
int MMDB_read_node(MMDB_s *const mmdb, uint32_t node_number, MMDB_search_node_s *const node);
0x03. 例子
例子1:使用 City 或 Country 库 查询IP所属位置信息
/* this file must be utf8 encode */
#include <errno.h>
#include <maxminddb.h>
#include <stdlib.h>
#include <string.h> int main(int argc, char **argv)
{
char *filename = "./GeoLite2-City.mmdb";
char *ip_address = "114.240.52.162"; MMDB_s mmdb;
MMDB_entry_data_s entry_data;
MMDB_lookup_result_s result;
int gai_error, mmdb_error; int status = MMDB_open(filename, MMDB_MODE_MMAP, &mmdb); if (MMDB_SUCCESS != status) {
fprintf(stderr, "Can't open %s - %s\n", filename, MMDB_strerror(status)); if (MMDB_IO_ERROR == status) {
fprintf(stderr, "IO error: %s\n", strerror(errno));
}
exit();
} result = MMDB_lookup_string(&mmdb, ip_address, &gai_error, &mmdb_error);
if ( != gai_error) {
fprintf(stderr, "Error from getaddrinfo for %s - %s\n", ip_address, gai_strerror(gai_error));
exit();
} if (MMDB_SUCCESS != mmdb_error) {
fprintf(stderr, "Got an error from libmaxminddb: %s\n", MMDB_strerror(mmdb_error));
exit();
} if (result.found_entry) {
/* 获取国家名称(简体中文) */
status = MMDB_get_value(&result.entry, &entry_data, "country", "names", "zh-CN", NULL);
/* 获取国家简称(CN/AU/JP等) */
// status = MMDB_get_value(&result.entry, &entry_data, "country", "iso_code", NULL); /* 获取城市名称(简体中文) */
// status = MMDB_get_value(&result.entry, &entry_data, "city", "names", "zh-CN", NULL); if (MMDB_SUCCESS == status) {/* MMDB_get_value 成功 */
if (entry_data.has_data) {/* 找到了想要的数据 */
if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
/* entry_data.utf8_string 返回的不是null-terminated 字符串,需要根据长度自己截取数据 */
fwrite(entry_data.utf8_string, entry_data.data_size, , stdout);
printf("\n");
}
else {
printf("data_type = %d\n", entry_data.type);
}
}
else {
fprintf(stderr, "MMDB_get_value not found\n");
}
}
else {
fprintf(stderr, "MMDB_get_value failed,%s\n", MMDB_strerror(status));
}
} MMDB_close(&mmdb);
exit(EXIT_SUCCESS);
}
例子2: 使用ASN库查询IP地址的ASN
/* this file must be utf8 encode */
#include <errno.h>
#include <maxminddb.h>
#include <stdlib.h>
#include <string.h> int main(int argc, char **argv)
{
char *filename = "./GeoLite2-ASN.mmdb";
char *ip_address = "114.240.52.162"; MMDB_s mmdb;
MMDB_entry_data_s entry_data;
MMDB_lookup_result_s result;
int gai_error, mmdb_error; int status = MMDB_open(filename, MMDB_MODE_MMAP, &mmdb); if (MMDB_SUCCESS != status) {
fprintf(stderr, "Can't open %s - %s\n", filename, MMDB_strerror(status)); if (MMDB_IO_ERROR == status) {
fprintf(stderr, "IO error: %s\n", strerror(errno));
}
exit();
} result = MMDB_lookup_string(&mmdb, ip_address, &gai_error, &mmdb_error);
if ( != gai_error) {
fprintf(stderr, "Error from getaddrinfo for %s - %s\n", ip_address, gai_strerror(gai_error));
exit();
} if (MMDB_SUCCESS != mmdb_error) {
fprintf(stderr, "Got an error from libmaxminddb: %s\n", MMDB_strerror(mmdb_error));
exit();
} /* ASN DB INFO Example
{
"autonomous_system_number":
4808 <uint32>
"autonomous_system_organization":
"China Unicom Beijing Province Network" <utf8_string>
}
*/
if (result.found_entry) {
/* 获取ASN */
status = MMDB_get_value(&result.entry, &entry_data, "autonomous_system_organization", NULL); if (MMDB_SUCCESS == status) {/* MMDB_get_value 成功 */
if (entry_data.has_data) {/* 找到了想要的数据 */
if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
/* entry_data.utf8_string 返回的不是null-terminated 字符串,需要根据长度自己截取数据 */
fwrite(entry_data.utf8_string, entry_data.data_size, , stdout);
printf("\n");
}
else {
printf("data_type = %d\n", entry_data.type);
}
}
else {
fprintf(stderr, "MMDB_get_value not found\n");
}
}
else {
fprintf(stderr, "MMDB_get_value failed,%s\n", MMDB_strerror(status));
}
} MMDB_close(&mmdb);
exit(EXIT_SUCCESS);
}
0x04. 题外话
从源代码来看, 官方提供的库并不适合高速大量查询, 所以有很多第三方实现,例如nginx等都自己实现了数据库的搜索模块.
GeoIP的使用-C语言版的更多相关文章
- libnode 0.4.0 发布,C++ 语言版的 Node.js
libnode 0.4.0 支持 Windows ,提升了性能,libuv 更新到 0.10.17 版本,libj 更新到 0.8.2 版本. libnode 是 C++ 语言版的 Node.js,和 ...
- md5加密算法c语言版
from: http://blog.sina.com.cn/s/blog_693de6100101kcu6.html 注:以下是md5加密算法c语言版(16/32位) ---------------- ...
- 基于gSOAP使用头文件的C语言版web service开发过程例子
基于gSOAP使用头文件的C语言版web service开发过程例子 一服务端 1 打开VS2005,创建一个工程,命名为calcServer. 2 添加一个头文件calc.h,编辑内容如下: 1// ...
- Windows 8.1 with Update 镜像下载(增OEM单语言版)
该系统已有更新的版本,请转至<Windows 8.1 with update 官方最新镜像汇总>下载. 2014年4月9日凌晨,微软向MSDN订阅用户开放了Windows 8.1 with ...
- 数据结构C语言版 有向图的十字链表存储表示和实现
/*1wangxiaobo@163.com 数据结构C语言版 有向图的十字链表存储表示和实现 P165 编译环境:Dev-C++ 4.9.9.2 */ #include <stdio.h> ...
- 数据结构C语言版 表插入排序 静态表
数据结构C语言版 表插入排序.txt两个人吵架,先说对不起的人,并不是认输了,并不是原谅了.他只是比对方更珍惜这份感情./* 数据结构C语言版 表插入排序 算法10.3 P267-P270 编译 ...
- 数据结构C语言版 弗洛伊德算法实现
/* 数据结构C语言版 弗洛伊德算法 P191 编译环境:Dev-C++ 4.9.9.2 */ #include <stdio.h>#include <limits.h> # ...
- VS2015如何新建C++或者C语言版的lib文件
当我们不想公开我们的代码的时候,可以把我们的代码封装成静态数据连接库,即lib文件.下面介绍下如何生成lib文件. 以VS2015为例,一种是C++版的lib文件,一种是C语言版的lib文件. 一.按 ...
- hbase rest api接口链接管理【golang语言版】
# go-hbase-resthbase rest api接口链接管理[golang语言版]关于hbase的rest接口的详细信息可以到官网查看[http://hbase.apache.org/boo ...
随机推荐
- Linux系统下root密码遗忘等系统故障的修复方法 - 运维总结
IDC机房有一台centos系统的服务器,由于这台服务器的系统装了好长时间,且root密码中间更新过几次,后面去机房现场维护时,登陆密码遗忘了,悲催啊~没办法,只能开机进入“单用户模式”进行密码重置了 ...
- VIJOS-P1232 核电站问题
VIJOS-P1232 核电站问题 JDOJ 1373 https://neooj.com/oldoj/problem.php?id=1373 题目描述 一个核电站有N个放核物质的坑, ...
- C# 一般处理程序
public class Three : IHttpHandler{ public void ProcessRequest(HttpContext context) { context.Respons ...
- 5-ESP8266 SDK开发基础入门篇--了解一下操作系统
对于操作系统不知道有没有害怕接触的... 先说一下操作系统是什么意思,其实咱的电脑就运行了操作系统,手机,等等... 操作系统和任务分不开,所谓任务就是一个一个的执行各个功能的函数,,,操作系统呢就是 ...
- [RN] windows7 安装 Realm Studio 后,打开报错 A JavaScript error occurred in the main process
windows7 安装 Realm Studio 后,打开报错 报错如下: A JavaScript error occurred in the main process Uncaught Exce ...
- linux下python解释器的sys.path路径如何添加
一. 使用pycharm时, pycharm会自动把我们新建的每个项目都加入到sys.path路径中, 我们在使用过程中根本不涉及 项目路径的处理, 但是当项目部署到linux上时, 问题就来了, l ...
- requests--重定向,序列化
重定向 默认情况下,除了 HEAD, Requests 会自动处理所有重定向.可以使用响应对象的 history 方法来追踪重定向. Response.history 是一个 Response 对象的 ...
- SPOJ31428 FIBONOMIAL(斐波那契数列)
神鱼推题,必是好题. 前几天刚做过[BJOI2019]勘破神机,于是就会这题了.(BJ人民强啊……%鱼) 首先要求是 $$\sum\limits_{i=0}^nx^if_i$$ 应该很明显能想到把 $ ...
- [LeetCode] 390. Elimination Game 淘汰游戏
There is a list of sorted integers from 1 to n. Starting from left to right, remove the first number ...
- 分布式共识算法 (三) Raft算法
系列目录 分布式共识算法 (一) 背景 分布式共识算法 (二) Paxos算法 分布式共识算法 (三) Raft算法 分布式共识算法 (四) BTF算法 一.引子 1.1 介绍 Raft 是一种为了管 ...