关于sqlite

1. php的sqlite的模块依然使用的是sqlite2, pdo_sqlite 模块使用的是sqlite3
2. sqlite的写操作是非常慢的,如下:
见表语句:
CREATE TABLE wireless (v varchar(16), PRIMARY KEY(v));

插入10000个IP:

插入速度如下:
$ php sqlite.php -t init
1000:45.657394886017
2000:91.184693813324
3000:133.79960989952
4000:176.86646604538
5000:218.9348089695
6000:261.49800491333
7000:306.47433900833
8000:351.71416902542
9000:397.750207901
9243:408.81617093086

结论:
1. 在所测试的数据量内, 每 1000 条数据需要45s时间, 即 45ms/条  , 应该算是比较慢的。

下面测试一个没有主键时的插入情况:
$ php sqlite.php -t init -d test.db
1000:41.796957969666
2000:83.843362092972
3000:125.97302293777
4000:169.13699889183
5000:212.61609697342
6000:255.99843406677
7000:299.17888689041
8000:339.0949409008
9000:376.1487801075
9243:385.17269802094

结论:
1. 每 1000 条数据需要41s时间, 即 41ms/条, 没有明显变化, 依然很慢
2. 虽然没有索引,但是查询速度却一点也没有变慢

关于黑白名单的实现

就在不经意的时候,我们可能会把一个大的黑白名单直接写在PHP的数组中,然后使用in_array判断指定的值是否在名单内,这样每次查一个值是否存在则都需要将所有的名单值构造成一个数组,然后遍历这个数组来查找,相当于我们要遍历两次名单而且其中一次是需要字符串比较的。

首先补充一点知识:PHP数组的构造是通过执行字节码来完成的,如arr.php:

  1. <?php
  2. $arr = array(
  3.         "a" => "AAA",
  4.         "a1" => array( "AAA","MMM"),
  5.         "b" => "BBB",
  6.         );

编译后生成的字节码如下:

  1. # php -dvld.active=1 d.php
  2. Finding entry points
  3. Branch analysis from position: 0
  4. Return found
  5. filename:       arr.php
  6. function name:  (null)
  7. number of ops:  7
  8. compiled vars:  !0 = $arr
  9. line     # *  op                           fetch          ext  return  operands
  10. ———————————————————————————
  11.    3     0  >   INIT_ARRAY                                       ~0      ‘AAA’, ‘a’
  12.    4     1      INIT_ARRAY                                       ~1      ‘AAA’
  13.          2      ADD_ARRAY_ELEMENT                                ~1      ‘MMM’
  14.          3      ADD_ARRAY_ELEMENT                                ~0      ~1, ‘a1’
  15.    5     4      ADD_ARRAY_ELEMENT                                ~0      ‘BBB’, ‘b’
  16.    6     5      ASSIGN                                                   !0, ~0
  17.    7     6    > RETURN                                                   1
  18. branch: #  0; line:     3-    7; sop:     0; eop:     6
  19. path #1: 0,

下面我们测试一下构造一个数组的效率,测试方法:
实验1. 在函数中构造一个大的数组,第一次执行之前做编译的工作,后续的执行则不需要编译,这样的话,我们调用函数1000次,则我们认为时间基本上是消耗在数组的构造上的;为了测试构造数组的时间,函数中最后return true, 而不是 return in_array($key, $arr);
实现2. 改造程序,将数组定义成static的,则数组只构造一次,其它和实验1相同
总结: (实验1的时间 – 实验2的时间) / 1000 则基本是构造一次数组需要的时间

测试代码test.php如下:

  1. <?php
  2. $s = microtime(1);
  3. for($i = 0 ; $i < 1000; $i++) {
  4.     check($i);
  5. }
  6. echo microtime(1) – $s;
  7. echo "\n";
  8. function check($key) {
  9. $arr = array(  // 一个很大的数组,1万个值的数组
  10. ‘value1’,
  11. ‘value2’,
  12. );
  13. return true;
        
  14. }

$ php test.php
5.1018438339233

执行时间6s

给 $arr 添加static 关键字,代码如下:

  1. <?php
  2. $s = microtime(1);
  3. for($i = 0 ; $i < 1000; $i++) {
  4.     check($i);
  5. }
  6. echo microtime(1) – $s;
  7. echo "\n";
  8. function check($key) {
  9. static $arr = array(  // 一个很大的数组,1万个值的数组
  10. ‘value1’,
  11. ‘value2’,
  12. );
  13. return true;
  14. }
        

执行时间:
#php test.php
0.0016131401062012

总结,根据实验的步骤中的公式来计算,可以知道初始化字节码执行时间约: 5s/1000 = 5ms ; 时间是比较长的

或许你会说如果将value当做key出现,则,可以使用 isset($arr[$key]) 而不是 in_array($key, $arr) 会更快些,应为in_array会遍历数组,而isset则是一个简单的hash就能判断。 但是问题是,如果将value当做key出现,则构造数组的时候,每个value都要做一个hash,这样构造数组的速度会更慢,如果每次构造能使用n次,则不失为一个好的办法,如果每次构造只使用一次,就有些不合算了。

下面对初始化1万个key的数组和1万个value的数组做一个比较:

1. 我们上面已近知道初始化1000次value的数组时间为5.1018438339233s
2. 下面我们通过如下函数测试初始化1000次key的数组的时间:

  1. function check2($key) {
  2.     $arr = array(
  3. "value1" => true,
  4. "value2" => true,
  5. );
  6. return true;
  7. }

#php test.php
4.8184480667114

这里和我预想的不一样了,虽然每个key都要做hash,但是,初始化数组耗费的时间反而更少(虽然差别不大),稍后再研究一下初始化数组的内部逻辑

下面看一下如下代码的字节码:

 
  1. <?php
  2. $arr = array(
  3.     "a" => "AAA",
  4.     "a1" => array"AAA","MMM"),
  5.     "b" => "BBB",
  6.     );
  7. $arr2 = array(1,2,3,4);
 
  1. $ php -dvld.active=1 d.php
  2. Finding entry points
  3. Branch analysis from position: 0
  4. Return found
  5. filename:      xxx/d.php
  6. function name:  (null)
  7. number of ops:  12
  8. compiled vars:  !0 = $arr, !1 = $arr2
  9. line     # *  op                           fetch          ext  return  operands
  10. ———————————————————————————
  11.    3     0  >   INIT_ARRAY                                       ~0      ‘AAA’‘a’
  12.    4     1      INIT_ARRAY                                       ~1      ‘AAA’
  13.          2      ADD_ARRAY_ELEMENT                                ~1      ‘MMM’
  14.          3      ADD_ARRAY_ELEMENT                                ~0      ~1, ‘a1’
  15.    5     4      ADD_ARRAY_ELEMENT                                ~0      ‘BBB’‘b’
  16.    6     5      ASSIGN                                                   !0, ~0
  17.    7     6      INIT_ARRAY                                       ~3      1
  18.          7      ADD_ARRAY_ELEMENT                                ~3      2
  19.          8      ADD_ARRAY_ELEMENT                                ~3      3
  20.          9      ADD_ARRAY_ELEMENT                                ~3      4
  21.         10      ASSIGN                                                   !1, ~3
  22.    8    11    > RETURN                                                   1
  23. branch: #  0; line:     3-    8; sop:     0; eop:    11
  24. path #1: 0, 

似乎没有太大差别。。。

gdb 调试PHP

PHP的代码包中提供了一个 .gdbinit 的gdb脚本文件,里面提供了20多个 gdb 的自定义命令,用于方便PHP的调试,下面举几个例子:
测试脚本a.php:

 

gdb 调试命令:
———————–
gdb php
set args a.php
break sleep
r

———————–

1. print_cvs 打印当前执行环境中已编译的PHP变量, 如:


2. printzv 打印指定的PHP变量, 需要指定地址, 如:

(gdb) printzv 0x9543f98
[0x9543f98] (refcount=1) string(3): “MMM”
(gdb)

3. 打印PHP的函数调用栈, 如:
(gdb) zbacktrace
[0x95770a4] sleep(1) /usr/home/junjie2/a.php:11
[0x9576fe0] test(“phpor”) /usr/home/junjie2/a.php:7
(gdb)

4. print_ft 打印函数表( HashTable )
(gdb) set $eg = executor_globals
(gdb) print $eg.function_table  

$6 = (HashTable *) 0xa5bd450
(gdb) print_ft $eg.function_table
[0xa5bd450] {
“zend_version\0” => “zend_version”
“func_num_args\0” => “func_num_args”
“func_get_arg\0” => “func_get_arg”
“func_get_args\0” => “func_get_args”
“strlen\0” => “strlen”
“strcmp\0” => “strcmp”
“strncmp\0” => “strncmp”
“strcasecmp\0” => “strcasecmp”
“strncasecmp\0” => “strncasecmp”
“each\0” => “each”

学习
1. 通过阅读 print_cvs 命令的实现,可以知道可以通过prev_execute_data来打印上一执行空间的PHP变量,如:
(gdb) printzv *executor_globals.current_execute_data->prev_execute_data->CVs[1]  
[0x9543408] (refcount=1) string(3): “AAA”
(gdb) printzv *executor_globals.current_execute_data->prev_execute_data->CVs[2]  
[0x95433ec] (refcount=1) string(3): “BBB”
(gdb)

2. 通过 op_array->vars 来找到对应的变量的名字
(gdb) printf “%s\n” ,executor_globals.current_execute_data->prev_execute_data->op_array->vars[1].name  
a
(gdb) printf “%s\n” ,executor_globals.current_execute_data->prev_execute_data->op_array->vars[2].name  
b

3. 修改了一下 print_cvs 命令,通过参数来获取每个执行空间的PHP的已编译变量


 

使用方法:
———————
(gdb) zbacktrace  #查看PHP的调用栈
[0x8ce2078] sleep(1) /usr/home/junjie2/a.php:9
[0x8ce1fe0] test(“phpor”) /usr/home/junjie2/a.php:5
(gdb) print_cvs #查看当前执行空间中的PHP变量
Compiled variables count: 3
0 = name
[0x8cae3d0] (refcount=2) string(5): “phpor”
1 = m
[0x8cae93c] (refcount=1) string(3): “MMM”
2 = n
[0x8cae958] (refcount=1) string(3): “NNN”
(gdb) print_cvs 2 # 查看指定执行空间中的PHP变量, 这个参数给大了也没关系,最多打印最外层的PHP变量
Compiled variables count: 2
0 = a
[0x8cae408] (refcount=1) string(3): “AAA”
1 = b
[0x8cae3ec] (refcount=1) string(3): “BBB”
(gdb)
———————

4. 有时候需要借助环境变量,如:
(gdb) print_ft (HashTable *)0xa5bd450
A syntax error in expression, near `’.
(gdb) set $phpor=(HashTable *)0xa5bd450
(gdb) print_ft $phpor
[0xa5bd450] {
“zend_version\0” => “zend_version”
“func_num_args\0” => “func_num_args”
“func_get_arg\0” => “func_get_arg”
“func_get_args\0” => “func_get_args”
“strlen\0” => “strlen”
“strcmp\0” => “strcmp”
“strncmp\0” => “strncmp”

 

5. 打印指定的PHP变量

 

PHP 的一个错误

PHP 的一个错误

 
  1. <?php
  2. $fp = fopen("file.txt""r");
  3. if(!$fpexit;
  4. while(!feof($fp)) { //if fp not a resource then the while will no end forever
  5.         echo fgets($fp);
  6. }

http代理 & socket4 & socket5

HTTP代理:能够代理客户机的HTTP访问,主要是代理浏览器访问网页,它的端口一般为80、8080、3128等;
SOCKS 代理:SOCKS代理与其他类型的代理不同,它只是简单地传递数据包,而并不关心是何种应用协议,既可以是HTTP请求,所以SOCKS代理服务器比其他 类型的代理服务器速度要快得多。SOCKS代理又分为SOCKS4和SOCKS5,二者不同的是SOCKS4代理只支持TCP协议(即传输控制协议),而 SOCKS5代理则既支持TCP协议又支持UDP协议(即用户数据包协议),还支持各种身份验证机制、服务器端域名解析等。SOCK4能做到的 SOCKS5都可得到,但SOCKS5能够做到的SOCK4则不一定能做到,比如我们常用的聊天工具QQ在使用代理时就要求用SOCKS5代理,因为它需 要使用UDP协议来传输数据

rfc: http://www.ietf.org/rfc/rfc1928.txt

PHP 中实现输出重定向

如果使用PHP来写一个后台程序,可能会考虑在程序中将标准输入、标准输出、标准错误给重定向,这样就不需要在启动程序的时候来做重定向(因为这样很容易忘记);在C中有比较正规的写法,但是PHP中没有找见对应的实现,今天想到一个变通的办法:
1. 使用ob_start(function); 来设置自定义的输出过滤器
2. 使用set_error_handler(function); 来实现自定义的标准错误处理器

然后,在自定义函数中就可以随意处理了,实例:

 
  1. <?php
  2. function my_stdout_handler($string) {
  3.         file_put_contents("4.log"$string, FILE_APPEND);
  4.         return ;
  5. }
  6. function my_error_handler($errno$errstr$errfile$errline) {
  7.         if (!(error_reporting() & $errno)) {
  8.                 // This error code is not included in error_reporting
  9.                 return;
  10.         }
  11.         switch ($errno) {
  12.                 case E_USER_ERROR:
  13.                         echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
  14.                         echo "  Fatal error on line $errline in file $errfile";
  15.                         echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
  16.                         echo "Aborting…<br />\n";
  17.                         exit(1);
  18.                         break;
  19.                 case E_USER_WARNING:
  20.                         echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
  21.                         break;
  22.                 case E_USER_NOTICE:
  23.                         echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
  24.                         break;
  25.                 default:
  26.                         echo "Unknown error type: [$errno] $errstr<br />\n";
  27.                         break;
  28.         }
  29.         /* Don’t execute PHP internal error handler */
  30.         return true;
  31. }
  32. ob_start(my_stdout_handler);
  33. set_error_handler(my_error_handler);
  34. print_r(ob_list_handlers());
  35. trigger_error("something wrong", E_USER_WARNING);

shell 的source命令

man 中说到:
Read and execute commands from filename in the current shell environment and return the  exit status  of  the  last  command executed from filename.

例子:
—— a1.sh ——
#!/bin/sh
./b.sh  #如此执行则b.sh 必须有可执行权限
——————-
—— a2.sh ——
#!/bin/sh
. b.sh  #如此写法则b.sh 可以没有可执行权限
——————-
—— b.sh ——
#!/bin/sh
sleep 100
——————-

执行:
sh a1.sh   #进程中会看到 b.sh

sh a2.sh   #进程中不会看到 b.sh

PHP中利用popen实现并发

PHP不支持多线程编程,于是,当需要同时执行多个任务的时候,就有些力不从心了;对于有些任务可以通过异步的方式来处理,如: 同时执行多个http请求;对于有些计算型的需求,或者是异步实现很麻烦的时候就不好办了。
当我再一次看fork/exec/system/source等类似的命令或系统调用的时候,觉得他们和之间的异同比较有意思,比如:
fork: 创建子进程,当前进程继续运行
exec: 当前进程直接让位于要执行的程序,用不返回
system: 使用当前进程的环境,创建一个子进程,等待子进程执行完成后返回; system=fork+exec+waitpid

于是想到了PHP中的popen,该方法可以和执行一个外部命令,但是不阻塞当前进程,只需要使用循环去收集执行结果就行了。

对于web程序,通过异步的http请求将任务提交给本机的web server或许比popen更加合算一些。

一个检查偶发连接失败的脚本

情景:
从机器A到机器B发起连接,连接超时时间设置为1s,有一定概率的连接失败的情况,使用下面脚本来测试,不断连接,放过成功的连接,只显示失败的连接,并且显示连接失败的时间,脚本如下:

 
  1. while :; do r=nc -z -v -w 1 10.79.40.43 11231 2>&1 | grep -v succe;if [ "x$r" != "x" ]; then d=date +"%H:%M";echo -n "$d "echo $r; fi ;  done