純真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顯示數據庫版本
- 編譯:
- 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;
- }
運行測試結果:
本文出自 “一方有” 博客,請務必保留此出處http://yifangyou.blog.51cto.com/900206/617658