把纯真IP库读到内存,纯真IP库本来就是有序的,然后每次请求二分查找就行,44WIP查找十几次就搞定了
dispatch_mode最好写3,不然做服务的时候,会导致进程任务分配不均匀。
max_request 处理请求数量累加到达该值会重启处理进程,防止内存泄露
worker_num 根据内存和服务处理能力可以自己设置跑几个工作进程。
swoole.php
<?php require ‘ipmatch.php‘; class IpServer { protected $iptables; protected static $cityList = array ( 0 => array ( ‘bj‘ => ‘北京‘, ), 1 => array ( ‘sh‘ => ‘上海‘, ), 2 => array ( ‘tj‘ => ‘天津‘, ), 3 => array ( ‘cq‘ => ‘重庆‘, ), 4 => array ( ‘gz‘ => ‘广州‘, ‘sz‘ => ‘深圳‘, ‘dg‘ => ‘东莞‘, ‘zhuhai‘ => ‘珠海‘, ‘shantou‘ => ‘汕头‘, ‘foshan‘ => ‘佛山‘, ‘jiangmen‘ => ‘江门‘, ‘zhongshan‘ => ‘中山‘, ‘huizhou‘ => ‘惠州‘, ‘maoming‘ => ‘茂名‘, ‘shaoguan‘ => ‘韶关‘, ‘zhanjiang‘ => ‘湛江‘, ), 5 => array ( ‘cd‘ => ‘成都‘, ‘zigong‘ => ‘自贡‘, ‘luzhou‘ => ‘泸州‘, ‘deyang‘ => ‘德阳‘, ‘mianyang‘ => ‘绵阳‘, ‘nanchong‘ => ‘南充‘, ‘liangshan‘ => ‘凉山‘, ), 6 => array ( ‘hz‘ => ‘杭州‘, ‘nb‘ => ‘宁波‘, ‘wenzhou‘ => ‘温州‘, ‘jiaxing‘ => ‘嘉兴‘, ), 7 => array ( ‘gy‘ => ‘贵阳‘, ‘liupanshui‘ => ‘六盘水‘, ‘zunyi‘ => ‘遵义‘, ), 8 => array ( ‘sy‘ => ‘沈阳‘, ‘dl‘ => ‘大连‘, ‘anshan‘ => ‘鞍山‘, ‘fushun‘ => ‘抚顺‘, ), 9 => array ( ‘nj‘ => ‘南京‘, ‘su‘ => ‘苏州‘, ‘wx‘ => ‘无锡‘, ‘xuzhou‘ => ‘徐州‘, ), 10 => array ( ‘fz‘ => ‘福州‘, ‘xm‘ => ‘厦门‘, ‘putian‘ => ‘莆田‘, ), 11 => array ( ‘sjz‘ => ‘石家庄‘, ‘tangshan‘ => ‘唐山‘, ‘handan‘ => ‘邯郸‘, ‘xingtai‘ => ‘邢台‘, ‘baoding‘ => ‘保定‘, ‘zhangjiakou‘ => ‘张家口‘, ‘chengde‘ => ‘承德‘, ), 12 => array ( ‘zz‘ => ‘郑州‘, ‘luoyang‘ => ‘洛阳‘, ‘pingdingshan‘ => ‘平顶山‘, ‘jiaozuo‘ => ‘焦作‘, ‘hebi‘ => ‘鹤壁‘, ‘xinxiang‘ => ‘新乡‘, ‘anyang‘ => ‘安阳‘, ), 13 => array ( ‘cc‘ => ‘长春‘, ‘jilin‘ => ‘吉林‘, ), 14 => array ( ‘hrb‘ => ‘哈尔滨‘, ‘qiqihaer‘ => ‘齐齐哈尔‘, ‘jixi‘ => ‘鸡西‘, ‘hegang‘ => ‘鹤岗‘, ‘shuangyashan‘ => ‘双鸭山‘, ), 15 => array ( ‘jn‘ => ‘济南‘, ‘qd‘ => ‘青岛‘, ‘wei‘ => ‘威海‘, ‘zibo‘ => ‘淄博‘, ‘zaozhuang‘ => ‘枣庄‘, ‘dongying‘ => ‘东营‘, ‘yantai‘ => ‘烟台‘, ), 16 => array ( ‘hf‘ => ‘合肥‘, ‘wuhu‘ => ‘芜湖‘, ‘bengbu‘ => ‘蚌埠‘, ‘maanshan‘ => ‘马鞍山‘, ‘anqing‘ => ‘安庆‘, ), 17 => array ( ‘nn‘ => ‘南宁‘, ‘gl‘ => ‘桂林‘, ‘liuzhou‘ => ‘柳州‘, ‘wuzhou‘ => ‘梧州‘, ‘qinzhou‘ => ‘钦州‘, ), 18 => array ( ‘hn‘ => ‘海口‘, ‘sanya‘ => ‘三亚‘, ‘wuzhishan‘ => ‘五指山‘, ), 19 => array ( ‘nmg‘ => ‘呼和浩特‘, ‘baotou‘ => ‘包头‘, ‘wuhai‘ => ‘乌海‘, ), 20 => array ( ‘ty‘ => ‘太原‘, ‘datong‘ => ‘大同‘, ‘yangquan‘ => ‘阳泉‘, ‘changzhi‘ => ‘长治‘, ), 21 => array ( ‘yc‘ => ‘银川‘, ‘shizuishan‘ => ‘石嘴山‘, ‘wuzhong‘ => ‘吴忠‘, ‘guyuan‘ => ‘固原‘, ‘zhongwei‘ => ‘中卫‘, ), 22 => array ( ‘lz‘ => ‘兰州‘, ‘jinchang‘ => ‘金昌‘, ‘baiyin‘ => ‘白银‘, ‘tianshui‘ => ‘天水‘, ‘wuwei‘ => ‘武威‘, ), 23 => array ( ‘xa‘ => ‘西安‘, ‘tongchuan‘ => ‘铜川‘, ‘baoji‘ => ‘宝鸡‘, ‘xianyang‘ => ‘咸阳‘, ‘weinan‘ => ‘渭南‘, ), 24 => array ( ‘xn‘ => ‘西宁‘, ‘haidong‘ => ‘海东‘, ‘haibei‘ => ‘海北‘, ‘huangnan‘ => ‘黄南‘, ‘hainan‘ => ‘海南‘, ‘guoluo‘ => ‘果洛‘, ‘yushu‘ => ‘玉树‘, ‘haixi‘ => ‘海西‘, ), 25 => array ( ‘wh‘ => ‘武汉‘, ‘huangshi‘ => ‘黄石‘, ‘xiangfan‘ => ‘襄樊‘, ‘shiyan‘ => ‘十堰‘, ‘jingzhou‘ => ‘荆州‘, ‘yichang‘ => ‘宜昌‘, ‘jingmen‘ => ‘荆门‘, ), 26 => array ( ‘cs‘ => ‘长沙‘, ‘zhuzhou‘ => ‘株洲‘, ‘xiangtan‘ => ‘湘潭‘, ‘hengyang‘ => ‘衡阳‘, ‘shaoyang‘ => ‘邵阳‘, ), 27 => array ( ‘nc‘ => ‘南昌‘, ‘jingdezhen‘ => ‘景德镇‘, ‘pingxiang‘ => ‘萍乡‘, ‘jiujiang‘ => ‘九江‘, ), 28 => array ( ‘km‘ => ‘昆明‘, ‘qujing‘ => ‘曲靖‘, ‘yuxi‘ => ‘玉溪‘, ), 29 => array ( ‘xj‘ => ‘乌鲁木齐‘, ‘kelamayi‘ => ‘克拉玛依‘, ‘tulufan‘ => ‘吐鲁番‘, ‘hami‘ => ‘哈密‘, ‘hetian‘ => ‘和田‘, ), 30 => array ( ‘xz‘ => ‘拉萨‘, ‘changdu‘ => ‘昌都‘, ‘shannan‘ => ‘山南‘, ‘rikaze‘ => ‘日喀则‘, ‘naqu‘ => ‘那曲‘, ‘ali‘ => ‘阿里‘, ‘linzhi‘ => ‘林芝‘, ), ); protected static $provinceList = array ( 4 => ‘广东‘, 5 => ‘四川‘, 6 => ‘浙江‘, 7 => ‘贵州‘, 8 => ‘辽宁‘, 9 => ‘江苏‘, 10 => ‘福建‘, 11 => ‘河北‘, 12 => ‘河南‘, 13 => ‘吉林‘, 28 => ‘云南‘, 15 => ‘山东‘, 16 => ‘安徽‘, 17 => ‘广西‘, 18 => ‘海南‘, 19 => ‘内蒙古‘, 20 => ‘山西‘, 21 => ‘宁夏‘, 22 => ‘甘肃‘, 23 => ‘陕西‘, 24 => ‘青海‘, 25 => ‘湖北‘, 26 => ‘湖南‘, 27 => ‘江西‘, 14 => ‘黑龙江‘, 29 => ‘*‘, 30 => ‘*‘, ); function run() { $serv = swoole_server_create("192.168.2.165", 9898); swoole_server_set($serv, array( ‘worker_num‘ => 2, ‘max_request‘ => 10000, ‘dispatch_mode‘ => 3 )); swoole_server_handler($serv, ‘onWorkerStart‘, array($this, ‘onStart‘)); swoole_server_handler($serv, ‘onConnect‘, array($this, ‘onConnect‘)); swoole_server_handler($serv, ‘onReceive‘, array($this, ‘onReceive‘)); swoole_server_handler($serv, ‘onClose‘, array($this, ‘onClose‘)); swoole_server_handler($serv, ‘onWorkerStop‘, array($this, ‘onShutdown‘)); swoole_server_start($serv); } public function onStart($serv) { echo ‘read iptables from file‘.PHP_EOL; $this->iptables = file(‘ip.txt‘); } public function onConnect($serv, $fd, $from_id) { } public function onReceive($serv, $fd, $from_id, $data) { $ret = match_ip($data, $this->iptables, self::$cityList, self::$provinceList); $serv->send($fd, $ret); $serv->close($fd); } public function onClose($serv, $fd, $from_id) { } public function onShutdown($serv) { echo ‘shutdown server and free resource‘.PHP_EOL; unset($this->iptables); } } $server = new IpServer(); $server->run();
有一点需要注意下的PHP 的整形是个有符号的Long 所以溢出时候会产生负数,需要处理一下。
ipmatch.php
<?php function ip2num($ip) { return sprintf("%u",ip2long($ip)); } function match_ip($ip, $data, $cityList, $provinceList) { $count = count($data); $ip = ip2num($ip); $i=0; $j=$count-1; while ($i<=$j) { $mid = intval(($i+$j)/2); $arr = preg_split(‘/\s+/‘, $data[$mid]); $midip = ip2num(trim($arr[0])); if ($midip>$ip) { $j = $mid-1; } else if ($midip<$ip) { $i = $mid+1; } else { break; } } $temp = preg_split(‘/\s+/‘, $data[$j]); $beginip = ip2num(trim($temp[0])); $endip = ip2num(trim($temp[1])); if ($ip>=$beginip&&$ip<=$endip) { unset($temp[0]); unset($temp[1]); foreach ($temp as $t) { $keyword = trim($t); if (!empty($keyword)) { break; } } $keyword = iconv(‘GBK‘, ‘UTF-8‘, $keyword); //和省匹配 foreach ($provinceList as $code=>$name) { if (strstr($keyword, $name)) { $provinceName = $name; $provinceCode = $code; break; } } if (isset($provinceCode)) { //和省内城市匹配 foreach ($cityList[$provinceCode] as $code=>$name) { if (strstr($keyword, $name)) { return ‘1:‘.$code.‘:‘.$name; } } } else { //和所有城市匹配 foreach ($cityList as $arr) { foreach ($arr as $code=>$name) { if (strstr($keyword, $name)) { return ‘1:‘.$code.‘:‘.$name; } } } } if (isset($provinceCode)) { return ‘2:‘.$provinceCode.‘:‘.$provinceName; } return ‘-1::‘; } } ?>
启动SERVER
php swoole.php
测试
client.php
class runtime { var $StartTime = 0; var $StopTime = 0; function get_microtime() { list($usec, $sec) = explode(‘ ‘, microtime()); return ((float)$usec + (float)$sec); } function start() { $this->StartTime = $this->get_microtime(); } function stop() { $this->StopTime = $this->get_microtime(); } function spent() { return round(($this->StopTime - $this->StartTime) * 1000, 1); } } function ip2City($ip) { $fp = fsockopen("192.168.2.165",9898, $errno, $errstr, 1); if (!$fp) { echo $errno . $errstr.PHP_EOL; } else { fwrite($fp, $ip); $out = ‘‘; while (!feof($fp)) { $out .= fgets($fp).PHP_EOL; } } fclose($fp); return $out; } $test = array(); $notMatch = array(); //读文件 $ips = file(‘access_wap_misc_20140115_part1.log‘); foreach ($ips as $line) { $ip = explode(‘ ‘, $line); $test[] = $ip[0]; } ////随即生成 //for ($i=0;$i<10000;$i++) { // $test[] = rand(1,255).‘.‘.rand(1,255).‘.‘.rand(1,255).‘.‘.rand(1,255); //} ////指定IP //$test[] = ‘122.11.37.92‘; $match_count = 0; $runtime= new runtime; $runtime->start(); foreach ($test as $ip) { $city = ip2City($ip); if (strlen($city)>1) { // $arr = explode(‘:‘,$city); echo $ip .‘ locate ‘. $city; $match_count++; }else { $notMatch[] = $ip; } } $runtime->stop(); echo "match ".count($test)." ips total run time: ".$runtime->spent()." milliseconds".PHP_EOL; echo "hits ganji citys:".$match_count .‘ ips‘.PHP_EOL;
经测试一个IP匹配1.5毫秒。注意,PHP 的各种socket都是秒级超时,不知道是不是和select的默认最小超时时间是1秒有关系,只有高版本的curl库支持毫秒级超时。
总的来说 swoole 可以让PHP以很低编程成本起个服务。为这么草根的语言添加了新生活力~