使用共享内存查询纯真IP库(微秒级)
c
2016-05-26

纯真IP库是网上一种比较完整的常用的ip库,基本上每5天更新一次。我写了个程序通过把 ip库加载到共享内存里,在42万条数据下,单次查询能够达到微秒级。

/*
iplocation.c
功能:本程序是把qq纯真ip数据库文件加载到共享内存里,通过参数查找出对应的所属的ip段,和地理位置,使用共享内存可以使查询一次在纳秒级。
      qq纯真ip数据库文件格式可以查看:http://lumaqq.linuxsir.org/article/qqwry_format_detail.html
      qq纯真ip数据库官网下载地址:http://www.cz88.net/fox/ipdat.shtml,需要安装,安装完后把qqwry.dat拷出即可,也可从网上找。

作者:yifangyou

成功运行环境:
CentOS 5 i386
gcc version 4.1.2 20071124 (Red Hat 4.1.2-42)

本次测试使用的ip库是
-------------------------
记录总数:429555条
更新日期:2011年06月05日
数据库版本:纯真

输入参数:ip
    当输入255.255.255.255显示数据库版本

ubuntu :
    apt-get install build-essential

编译:
    gcc -o iplocation iplocation.c

运行:
    [root@localhost ~]# ./iplocation 58.62.69.255
    ip=58.62.69.255 is between 58.62.64.0,58.62.69.255
    location:广东省广州市番禺区 电信
    [root@localhost ~]# ./iplocation 184.73.255.255
    ip=184.73.255.255 is between 184.72.0.0,184.73.255.255
    location:美国 弗吉尼亚州AmazonEC2东海岸数据中心
    [root@localhost ~]# ./iplocation 255.255.255.255
    ip=255.255.255.255 is between 255.255.255.0,255.255.255.255
    location:纯真网络 2011年06月05日IP数据
    [root@localhost ~]# ./iplocation 0.0.0.0
    ip=0.0.0.0 is between 0.0.0.0,0.255.255.255
    location:IANA保留地址 CZ88.NET
*/
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <math.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <errno.h>
#define SHARE_MEMORY_FILE "/tmp/qqwry.dat" //共享内存路径.ip库路径
#define UNKNOWN "Unknown"
#define SHARE_MEMORY_SIZE 10485760 //必须比ip库文件大
#define INET6_ADDRSTRLEN 46
#define RECORD_LEN 7 //单条记录长度
//共享内存指针
char *p_share;
//第一条记录指针
char *p_begin;
char *p_end;
//总记录数
long total_record;

//结果集
typedef struct
{
    char *p_country;
    char *p_area;
    char beginip[INET6_ADDRSTRLEN]; // 用户IP所在范围的开始地址
    char endip[INET6_ADDRSTRLEN]; // 用户IP所在范围的结束地址
}location;
//把4字节转为整数
unsigned long getlong4(char *pos) //将读取的4个字节转化为长整型数
{
    unsigned long result=(((unsigned char )(*(pos+3)))<<24)
        +(((unsigned char )(*(pos+2)))<<16)
        +(((unsigned char )(*(pos+1)))<<8)
        +((unsigned char )(*(pos)));
    return result;
}
//把3字节转为整数
unsigned long getlong3(char *pos) //将读取的3个字节转化为长整型数
{
    unsigned long result=(((unsigned char )(*(pos+2)))<<16)
        +(((unsigned char )(*(pos+1)))<<8)
        +((unsigned char )(*(pos)));
    return result;
}

/**
 * 创建共享内存,并加载ip库进去
 *
 * @return void
 */
void createshare()
{
    int fd;
    long filesize=0;
    FILE *fp=fopen(SHARE_MEMORY_FILE,"rb");
    //读取文件长度
    fseek(fp,0,SEEK_END);
    filesize=ftell(fp);
    //归零
    fseek(fp,0,SEEK_SET);
    //获得文件描述符,用于生成共享内存
    fd=open(SHARE_MEMORY_FILE,O_CREAT|O_RDWR|O_TRUNC,00777);
    p_share = (char*) mmap(NULL,SHARE_MEMORY_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
    lseek(fd,0,SEEK_SET);
    //把文件内容读入共享内存
    fread(p_share,filesize,1,fp);
    fclose(fp);
    close(fd);
}

/**
 * 打开共享内存指针
 *
 * @return void
 */
void openshare() // map a normal file as shared mem:
{
    int fd;
    fd=open(SHARE_MEMORY_FILE,O_RDWR,00777);
    //打开共享内存
    p_share = (char*)mmap(NULL,SHARE_MEMORY_SIZE,PROT_READ,MAP_SHARED,fd,0);
    if(p_share==MAP_FAILED)
    {
        //若是不存在则创建
        createshare();
    }
    close(fd);
    //第一条记录位置
    p_begin=p_share+getlong4(p_share);
    //最后一条记录位置
    p_end=p_share+getlong4(p_share+4);
    //记录总数
    total_record=(getlong4(p_share+4)-getlong4(p_share))/RECORD_LEN;
}

/**
 * 关闭共享内存指针
 *
 * @return void
 */
void closeshare()
{
    munmap( p_share, SHARE_MEMORY_SIZE);
}

/**
 * 返回地区信息
 *
 * @char *pos 地区的指针
 * @return char *
 */
char *getarea(char *pos) {
    char *byte=pos; // 标志字节
    pos++;
    switch (*byte) {
    case 0: // 没有区域信息
        return UNKNOWN;
        break;
    case 1:
    case 2: // 标志字节为1或2,表示区域信息被重定向
        return p_share+getlong3(pos);
        break;
    default: // 否则,表示区域信息没有被重定向
        return byte;
        break;
    }
}
//获得ip所属地理信息,isp
void getipinfo(char *ipstr,location *p_loc)
{
    char *pos = p_share;
    int record_len=10;
    char *firstip=0; // first record position
    //把ip转为整数
    unsigned long ip=htonl(inet_addr(ipstr));
    firstip=p_begin;

    long l=0;
    long u=total_record;
    long i=0;
    char* findip=firstip;
    unsigned long beginip=0;
    unsigned long endip=0;
    //二分法查找
    while(l <= u)
    {
        i=(l+u)/2;
        pos=firstip+i*RECORD_LEN;
        beginip = getlong4(pos);
        pos+=4;
        if(ip<beginip)
        {
            u=i-1;
        }
        else
        {
            endip=getlong4(p_share+getlong3(pos));
            if(ip>endip)
            {
                l=i+1;
            }
            else
            {
                findip=firstip+i*RECORD_LEN;
                break;
            }
        }
    }
    long offset = getlong3(findip+4);
    pos=p_share+offset;
    endip= getlong4(pos); // 用户IP所在范围的结束地址
    pos+=4;

    unsigned long j=ntohl(beginip);
    inet_ntop(AF_INET,&j,p_loc->beginip, INET6_ADDRSTRLEN);// 获得开始地址的IP字符串类型
    j=ntohl(endip);
    inet_ntop(AF_INET,&j,p_loc->endip, INET6_ADDRSTRLEN);// 获得结束地址的IP字符串类型

    char *byte = pos; // 标志字节
    pos++;
    switch (*byte) {
    case 1:{ // 标志字节为1,表示国家和区域信息都被同时重定向
        long countryOffset = getlong3(pos); // 重定向地址
        pos+=3;
        pos=p_share+countryOffset;
        byte = pos; // 标志字节
        pos++;
        switch (*byte) {
        case 2: // 标志字节为2,表示国家信息又被重定向
        {
            p_loc->p_country=p_share+getlong3(pos);
            pos=p_share+countryOffset+4;
            p_loc->p_area = getarea(pos);
        }
        break;
        default: // 否则,表示国家信息没有被重定向
        {
            p_loc->p_country=byte;
            p_loc->p_area = getarea(p_loc->p_country+strlen(p_loc->p_country)+1);
        }
        break;
        }
    }
        break;
    case 2: // 标志字节为2,表示国家信息被重定向
    {
        p_loc->p_country=p_share+getlong3(pos);
        p_loc->p_area=p_share+offset+8;
    }
    break;
    default:{ // 否则,表示国家信息没有被重定向
        p_loc->p_country=byte;
        p_loc->p_area=getarea(p_loc->p_country+strlen(p_loc->p_country)+1);
    }
        break;
    }

}
int main(int argc, char** argv)
{
    if(argc<2)
    {
        printf("please enter the checked ip.\n");
    }
    location loc={0};
    //打开共享内存
    openshare();
    getipinfo(argv[1],&loc);
    printf("ip=%s is between %s,%s\n",argv[1],loc.beginip,loc.endip);
    printf("location:%s %s\n",loc.p_country,loc.p_area);
    //关闭共享内存
    // closeshare();
    return 0;
}
其它文章