docker下安装php拓展swoole
docker下安装php拓展,docker下可用的命令少,安装起来可没那么方便了,我这里用的ubuntu系统,安装了集成环境dnmp
若是一开始知道要用到swoole拓展,安装dnmp的时候直接改配置文件.env就得了,省事省力.下面就记录下在 php容器中安装php拓展.
进入我的php容器
sudo docker exec -it php /bin/sh
用php -m查看当前已经安装好的拓展,没有swoole
pecl install swoole
很不幸的是报错了,大概是说找不到autoconf 后来百度了一番安装autoconf的教程,还得安装m4
本来docker容器就应该精简轻巧为主,安装这么多花里胡哨的东西干嘛呢,再者说docker-compose安装php其他拓展的时候如果少了m4应该会自己装上,所以肯定还有其他办法的.
后来了解到PHP中安装拓展有几个特殊的命令
docker-php-sourcedocker-php-ext-installdocker-php-ext-enabledocker-php-ext-configure用这些命令应该就可以了,之前玩过windows下的docker,那个安装php拓展直接用install-php-extensions 就可以了
找到刚才安装失败的swoole拓展包find / -name "swoole-4.5.2.tgz"看到下载到了
/tmp/pear/download/swoole-4.5.2.tgz
进入该目录
cd /tmp/pear/download
将包解压,移动到指定的目录 /usr/src/php/ext,如果没有这个目录用 docker-php-source extract来生成
tar zxvf swoole-4.5.2.tgz
mv swoole-4.5.2 /usr/src/php/ext/
cd /usr/src/php/ext/
我们发现这里的文件夹都是不带后面的版本号的,这个 swoole-4.5.2我们改成 swoole
mv swoole-4.5.2 swoole
然后执行docker-php-ext-install swoole就行了,让它慢慢编译就好了,完了用php -m查看下就发现swoole拓展有了
最后退出容器后,记得重启php服务啊!记得重启php服务啊!记得重启php服务啊!
mosquitto是一种使用物联网mqtt协议发送和接收数据的服务器,可以在php的开发过程中使用扩展调用接口,访问mosquitto服务器,然后通过mqtt协议发送和接收设备数据。
php使用的扩展名称为mosquitto,在CentOS 7上没有RPM包安装,安装扩展需要从源代码编译。
从github上mgdm/Mosquitto-PHP仓库复制一份代码,编译前需要已安装php的开发工具和包含使用的头文件,比如已安装php71w-devel包,运行which phpize可以找到phpize开发工具。
进入代码目录,运行phpize,生成了configure文件,然后运行当前目录的./configure,检查编译环境和头文件。检查完成后,可以编译源代码,运行命令make编译源代码。如果提示缺少头文件,那需要检查是否已安装相应软件的开发包。
编译完成后,在modules目录下生成mosquitto.so扩展文件,复制到php的扩展目录,比如/usr/lib64/php/modules/。在php.ini配置文件中增加extension=mosquitto.so加载扩展文件,运行php -m | grep mosquitto可以查看扩展是否已安装。
然后使用php调用mosquitto扩展发送和接收消息,进入Mosquitto-PHP中examples目录,
pub.php文件中为发送和接收消息的示例代码,
$client->onMessage('message'); //设置消息接收处理函数
$client->subscribe('/#', 1); //订阅关联子主题
$mid = $client->publish('/hello', "Hello from PHP at " . date('Y-m-d H:i:s'), 1, 0); //发送消息
运行php pub.php测试消息的发送接收,执行中会显示步骤的调试信息。
(1)下载swoole安装包到本地(下载到root家目录下)(2)解压并进入该目录tar zxf v1.10.5.tar.gzcd swoole-src-1.10.5/(3)使用phpize来生成php编译配置(phpize路径在php安装目录的bin目录下,视实际安装路径而定)/www/wdlinux/nginx_php/bin/phpize出现类似以下内容就说明成功:Configuring for:PHP Api Version: 20121113Zend Module Api No: 20121212Zend Extension Api No: 220121212注意:这里有使用yum安装的php环境,直接运行phpize,可能会出现如下错误:Can't find PHP headers in /usr/include/phpThe php-devel package is required for use of this command.这是因为现在安装的php库不匹配现在的php版本,根据php -v得知当前版本,并依据版本yum安装php-devel,例如:yum install -y php55w-devel(4)使用./configure来做编译配置检测,没有出现error就说明成功./configure --with-php-config=/www/wdlinux/nginx_php/bin/php-config --enable-openssl注意:php-config的路径视实际情况而定。如果yum安装的php环境,只需要直接运行./configure --enable-openssl(5)使用make进行编译 make(6)使用make install进行安装make install没有出现error说明没有问题,make install成功后会生成一个Installing shared extensions及路径。例如:Installing shared extensions: /usr/lib64/php/modules/说明在这个路径下生成了swoole.so把 extension = /usr/lib64/php/modules/swoole.so复制到php.ini文件中(php.ini文件可以在phpinfo中或者使用php -i |grep php.ini命令查看)(7)重启php服务,使用phpinfo或者php -m查看是否安装成功
1、打开php官方扩展地址:https://pecl.php.net/
2、搜索自己想要安装的扩展
3、搜索到结果后,点击 redis 进入到详情页面
4、下载相应版本的扩展包,
使用命令解压
tar -zxvf ./redis-5.1.1.tgz -C /usr/local/src/
5、进入到解压的源码文件夹
cd /usr/local/src/redis-5.1.1/
6、执行 phpize 命令
/usr/local/php/bin/phpize
如果不知道 phpize 命令的路径,可以执行以下命令搜索下
whereis phpize
7、最后执行以下命令
./configure --with-php-config=/usr/local/php/bin/php-config make && make install
/usr/local/php 为 PHP 的安装目录
编译安装完成之后,出现下面类似的安装路径:
/usr/local/php/lib/php/extensions/debug-non-zts-20131226/
8、修改 php.ini 文件,增加以下代码
extension=/usr/local/php/lib/php/extensions/debug-non-zts-20131226/redis.so
最后重启php即可。
所有动态添加的php扩展,都是这个步骤,先进入源码文件夹,然后执行:
> /usr/local/php/bin/phpize
> ./configure --with-php-config=/usr/local/php/bin/php-config
> make && make install
> vim php.ini
extension= ... ...
最后重新加载PHP配置文件或重启
CSpeed 框架,全堆栈的轻量级C语言PHP扩展框架,以高性能为目标、极速为目标。
对于图形界面开发,太多语言有着自己的实现,不过大部分语言采用的都是绑定实现的,因为大部分图形库都是基于c/c++开发的,而众多语言都提供了和c交互的接口,所以我们就可以通过这些接口来实现一个语言的图形界面开发。
我们都知道php的底层是c语言实现的,并且它的扩展也是使用c语言来开发的,而且它的大部分功能都是通过扩展来实现的,因此,它也是可以通过扩展库来实现图形界面开发的,今天,我们就来盘点php开发图形界面的扩展库。
php-gtk2
gtk是一个免费开源的图形库,它有着丰富的api,有着出色组件接口,同时它是跨平台的,而php-gtk2就是php语言和gtk的绑定,通过安装php-gtk扩展,我们就可以使用php语言来编写图形界面。
php-ui
php7出现的时候,php官方推出了自己的图形UI库,其实它是基于libui绑定的扩展库。
libui也是c语言开发的,非常简单灵巧的图形库,它在各个操作系统下都有着出色的表现。
libui也是有着最多语言绑定的图形库,我们熟知的大部分语言都提供了和libui绑定的扩展库,从这点来看,足矣说明它的受欢迎度。
lazarus-php
这是一个基于lazarus的php库,它的强大之处在于你不需要安装任何其它的php扩展,只需要使用composer引入依赖,你就可以开发出一个图形界面程序。如果你没有接触过php图形界面开发,使用它可能是你最快上手php图形界面开发的方案。
php-sdl2
sdl是一款非常出色图形库,现在它的最新版本是2.2,通过使用它,我们可以轻松访问音频,访问鼠标,接入OpenGL图形库,接入Direct3D来访问显卡。
总结
相比较其它语言,php的图形扩展库确实少得可怜,不过这并不是说php就不能用来开发图形界面。在开发一些不需要高性能的图形界面的时候,任何语言都是可以做的,而我们需要的做的仅仅是找到最适合自己的那个语言,然后把它开发出来。
本文实例讲述了PHP扩展Swoole实现实时异步任务队列。分享给大家供大家参考,具体如下:
假如要发100封邮件,for循环100遍,用户直接揭竿而起,什么破网站!
但实际上,我们很可能有超过1万的邮件。怎么处理这个延迟的问题?
答案就是用异步。把“发邮件”这个操作封装,然后后台异步地执行1万遍。这样的话,用户提交网页后,他所等待的时间只是“把发邮件任务请求推送进队列里”的时间。而我们的后台服务将在用户看不见的地方跑。
在实现“异步队列”这点上,有人采用MySQL表或者redis来存放待发送的邮件,然后,每分钟定时读取待发送列表,然后处理。这便是定时异步任务队列。但当前提交的任务要一分钟后才能执行,在某些实时性要求应用场景里还是不快。有些场景要求,只有一提交任务,便马上执行,但用户不需要等待返回结果。
本文将探讨用php扩展swoole实现实时异步任务队列的方案。
服务端
在打算放置脚本的目录(你也可以自行新建)新建Server.php,代码如下
启动服务后,让我们看看如何调用服务。新建测试文件Client_test.php客户端
保存好代码,在命令行或者浏览器中执行Client_test.php,便实现了异步任务队列。你所填写的URL,将会在每次异步任务被提交后,以HTTP GET的方式异步执行。在上面代码中,url即为任务所在地址,param为所需传递参数。
本文转自鸟哥博客:https://www.laruence.com/2020/03/11/5475.html
随着PHP7.4而来的有一个我认为非常有用的一个扩展,PHP FFI(Foreign Function interface), 引用一段PHP FFI RFC中的一段描述:
For PHP, FFI opens a way to write PHP extensions and bindings to C libraries in pure PHP.
是的,FFI提供了高级语言直接的互相调用,而对于PHP来说,FFI让我们可以方便的调用C语言写的各种库。
其实现有大量的PHP扩展是对一些已有的C库的包装,比如常用的mysqli, curl, gettext等,PECL中也有大量的类似扩展。
传统的方式,当我们需要用一些已有的C语言的库的能力的时候,我们需要用C语言写wrapper,把他们包装成扩展,这个过程中就需要大家去学习PHP的扩展怎么写,当然现在也有一些方便的方式,比如Zephir. 但总还是有一些学习成本的,而有了FFI以后,我们就可以直接在PHP脚本中调用C语言写的库中的函数了。
而C语言几十年的历史中,积累了大量的优秀的库,FFI直接让我们可以方便的享受这个庞大的资源了。
言归正传,今天我用一个例子来介绍,我们如何使用PHP来调用libcurl,来抓取一个网页的内容,为什么要用libcurl呢?PHP不是已经有了curl扩展了么?嗯,首先因为libcurl的api我比较熟,其次呢,正是因为有了,才好对比,传统扩展方式和FFI方式直接的易用性不是?
首先,比如我们就拿当前你看的这篇文章为例,我现在需要写一段代码来抓取它的内容,如果用传统的PHP的curl扩展,我们大概会这么写:
<?php$url="https://www.laruence.com/2020/03/11/5475.html";$ch=curl_init();curl_setopt($ch, CURLOPT_URL,$url);curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,0);curl_exec($ch);curl_close($ch);(因为我的网站是https的,所以会多一个设置SSL_VERIFYPEER的操作)那如果是用FFI呢?
首先我们下载PHP-FFI, 编译安装,PHP-FFI需要PHP-7.4以及libffi-3以上。
然后,我们需要告诉PHP FFI我们要调用的函数原型是咋样的,这个我们可以使用FFI::cdef, 它的原型是:
FFI::cdef([string $cdef=""[, string $lib= null]]): FFI具体到这个例子,我们写一个curl.php, 包含所有要申明的东西,代码如下:
$libcurl= FFI::cdef(<<<CTYPEvoid *curl_easy_init();int curl_easy_setopt(void *curl, int option,...);int curl_easy_perform(void *curl);void curl_easy_cleanup(void *handle);CTYPE,"libcurl.so");在string $cdef中,我们可以写C语言函数式申明,FFI会parse它,了解到我们要在string $lib这个库中调用的函数的签名是啥样的,在这个例子中,我们用到三个libcurl的函数,它们的申明我们都可以在libcurl的文档里找到,比如对于curl_easy_init.
这里有个地方是,文档中写的是返回值是CURL *,但事实上因为我们的例子中不会引用它,只是传递,那就避免麻烦就用void *代替。
然而还有个麻烦的事情是,PHP预定义好了CURLOPT_等option的值,但现在我们需要自己定义,简单的办法就是查看curl的头文件,找到对应的值,然后我们把值给加进去:
<?phpconst CURLOPT_URL =10002;const CURLOPT_SSL_VERIFYPEER =64;$libcurl= FFI::cdef(<<<CTYPEvoid *curl_easy_init();int curl_easy_setopt(void *curl, int option,...);int curl_easy_perform(void *curl);void curl_easy_cleanup(void *handle);CTYPE,"libcurl.so");好了,定义部分就算完成了,现在我们完成实际逻辑部分,整个下来的代码会是:
<?phprequire"curl.php";$url="https://www.laruence.com/2020/03/11/5475.html";$ch=$libcurl->curl_easy_init();$libcurl->curl_easy_setopt($ch, CURLOPT_URL,$url);$libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER,0);$libcurl->curl_easy_perform($ch);$libcurl->curl_easy_cleanup($ch);怎么样,相比使用curl扩展的方式, 是不是一样简练呢?
接下来,我们稍微弄的复杂一点,也即使,如果我们不想要结果直接输出,而是返回成一个字符串呢, 对于PHP的curl扩展来说,我们只需要调用curl_setop 把CURLOPT_RETURNTRANSFER为1,但在libcurl中其实并没有直接返回字符串的能力,而是提供了一个WRITEFUNCTION的回掉函函数,在有数据返回的时候,libcurl会调用这个函数.
目前我们并不能直接把一个PHP函数作为回调函数通过FFI传递给libcurl, 那我们会有俩种方式来做:
1. 采用WRITEDATA, 默认的libcurl会调用fwrite作为回调函数,而我们可以通过WRITEDATA给libcurl一个fd,让它不要写入stdout,而是写入到这个fd2. 我们自己编写一个C到简单函数,通过FFI引入进来,传递给libcurl.
我们先用第一种方式,首先我们需要使用fopen,这次我们通过定义个C的头文件来申明原型(file.h):
void *fopen(char *filename, char *mode);void fclose(void * fp);像file.h一样,我们把所有的libcurl的函数申明也放到curl.h中去
#define FFI_LIB "libcurl.so"void*curl_easy_init();intcurl_easy_setopt(void*curl,int option,...);intcurl_easy_perform(void*curl);voidcurl_easy_cleanup(CURL*handle);注意, 我们通过定义了一个FFI_LIB的宏,来告诉FFI这些函数来自libcurl.so, 当我们用FFI::load加载这个h文件的时候,PHP FFI就会自动载入libcurl.so, 好,现在整个代码会是:
<?phpconst CURLOPT_URL =10002;const CURLOPT_SSL_VERIFYPEER =64;const CURLOPT_WRITEDATA =10001;$libc= FFI::load("file.h");$libcurl= FFI::load("curl.h");$url="https://www.laruence.com/2020/03/11/5475.html";$tmpfile="/tmp/tmpfile.out";$ch=$libcurl->curl_easy_init();$fp=$libc->fopen($tmpfile,"a");$libcurl->curl_easy_setopt($ch, CURLOPT_URL,$url);$libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER,0);$libcurl->curl_easy_setopt($ch, CURLOPT_WRITEDATA,$fp);$libcurl->curl_easy_perform($ch);$libcurl->curl_easy_cleanup($ch);$libc->fclose($fp);$ret=file_get_contents($tmpfile);@unlink($tmpfile);但这种方式呢就是需要一个临时的中转文件,还是不够优雅, 现在我们用第二种方式,要用第二种方式,我们需要自己用C写一个回掉函数传递给libcurl:
#include<stdlib.h>#include<string.h>#include"write.h"size_town_writefunc(void*ptr,size_tsize,size_tnmember,void*data){own_write_data*d =(own_write_data*)data;size_ttotal = size * nmember;if(d->buf == NULL){ d->buf =malloc(total);if(d->buf == NULL){return0;} d->size = total;memcpy(d->buf, ptr, total);}else{ d->buf =realloc(d->buf, d->size + total);if(d->buf == NULL){return0;}memcpy(d->buf + d->size, ptr, total); d->size += total;}return total;}void*init(){return&own_writefunc;}注意此处的init函数,因为在PHP FFI中,就目前的版本(2020-03-11)我们没有办法直接获得一个函数指针,所以我们定义了这个函数,返回own_writefunc的地址。
最后我们定义上面用到的头文件write.h:
#define FFI_LIB "write.so"typedefstruct_writedata{void*buf;size_tsize;} own_write_data;void*init();注意到我们在头文件中也定义了FFI_LIB, 这样这个头文件就可以同时被write.c和接下来我们的PHP FFI共同使用了。
然后我们编译write函数为一个动态库:
gcc -O2 -fPIC -shared -g write.c -o write.so好了, 现在整个的代码会变成:
<?phpconst CURLOPT_URL =10002;const CURLOPT_SSL_VERIFYPEER =64;const CURLOPT_WRITEDATA =10001;const CURLOPT_WRITEFUNCTION =20011;$libcurl= FFI::load("curl.h");$write= FFI::load("write.h");$url="https://www.laruence.com/2020/03/11/5475.html";$data=$write->new("own_write_data");$ch=$libcurl->curl_easy_init();$libcurl->curl_easy_setopt($ch, CURLOPT_URL,$url);$libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER,0);$libcurl->curl_easy_setopt($ch, CURLOPT_WRITEDATA, FFI::addr($data));$libcurl->curl_easy_setopt($ch, CURLOPT_WRITEFUNCTION,$write->init());$libcurl->curl_easy_perform($ch);$libcurl->curl_easy_cleanup($ch);ret = FFI::string($data->buf,$data->size);好了,跑一下吧?
然而毕竟直接在PHP中引用外部的so,还是会有很大的安全问题的,另外你也具备了1000中方法让PHP crash,安全起见我们可以采用preload的方式,这种模式下,我们不能在脚本中直接调用FFI::cdef,FF::load, 只能在通过opcache.preload:
ffi.enable=preloadopcache.preload=ffi_preload.incffi_preload.inc:
<?phpFFI::load("curl.h");FFI::load("write.h");但我们引用载入的FFI呢?为此我们需要修改一下这俩个.h头文件,加入FFI_SCOPE, 比如curl.h:
#define FFI_LIB "libcurl.so"#define FFI_SCOPE "libcurl"void*curl_easy_init();intcurl_easy_setopt(void*curl,int option,...);intcurl_easy_perform(void*curl);voidcurl_easy_cleanup(void*handle);对应的我们给write.h也加入FFI_SCOPE为"write", 然后我们的脚本现在看起来应该是这样:
<?phpconst CURLOPT_URL =10002;const CURLOPT_SSL_VERIFYPEER =64;const CURLOPT_WRITEDATA =10001;const CURLOPT_WRITEFUNCTION =20011;$libcurl= FFI::scope("libcurl");$write= FFI::scope("write");$url="https://www.laruence.com/2020/03/11/5475.html";$data=$write->new("own_write_data");$ch=$libcurl->curl_easy_init();$libcurl->curl_easy_setopt($ch, CURLOPT_URL,$url);$libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER,0);$libcurl->curl_easy_setopt($ch, CURLOPT_WRITEDATA, FFI::addr($data));$libcurl->curl_easy_setopt($ch, CURLOPT_WRITEFUNCTION,$write->init());$libcurl->curl_easy_perform($ch);$libcurl->curl_easy_cleanup($ch);ret = FFI::string($data->buf,$data->size);也就是,我们现在使用FFI::scope来代替FFI::load,引用对应的函数。
好了,经过这个例子,大家应该对FFI有了一个比较深入的理解了,有兴趣,就去找一个C库,试试吧?
本文的例子,你可以在我的github上下载到:FFI example
最后还是多说一句,例子只是为了演示功能,所以去掉了很多错误分支的判断捕获,大家自己写的时候还是要加入。使用FFI的话,确实让你会有1000种方式让PHP segfault crash,所以be careful
作为世界上最好的语言,php有着非常灵活的语法,它的灵活甚至有时候让你摸不到头脑,函数的命名不规范遭到了很多人的批评,然而这些并不能阻止它的发展。
没有一种语言从设计之初就可以做所有事情,因此我们需要不断去发展它,扩展它,而php的扩展就是它这些年不断前行的动力,yar,swoole的出现,让php又焕发出青春活力。
php的扩展开发主要有三种方式。
PHP-CPP
php-cpp是一套c++类库,方便我们用来开发php的扩展程序。它具有速度极快的特点。不需要知道zendengine的知识就可以开发扩展,并且可以访问php中的各种语法结构和变量。
因为php的很多函数都是来自于c语言命名,因此当你使用c++开发php的扩展的时候,你会感到很亲切,感到很熟悉,感到上手很容易。
Zephir
它提供了类型系统,以友好的语言将静态和动态类型结合在了一起,并使用行业标准的编译器将其编译为机器代码,具有高可维护的特点。
zephir是安全的,它禁止我们使用指针访问内存,并提供了垃圾回收器,进行垃圾回收,避免了内存泄漏。
它是跨平台的,可以支持多个系统平台。
ext_skel
这是php源码自带的扩展生成工具,使用它,可以快速搭建一个扩展框架。
使用此方法需要你有c语言的基本知识,会编译c代码。
使用phpize生成configure文件,进行编译安装。
总结
随着FFI的出现,php可以更加优雅的使用嵌套其它语言,也许以后,将会出现更多使用php原生语言开发出来的扩展。
一、主要内容:
1php扩展的概念和底层实现
2编写一个php扩展的步骤
3php底层,Zend 引擎API的介绍 ,HashTable 原理
二、相关概念
1.【php扩展】
php的插件,也就是php的扩展,因为php的底层就是C语言。
php –m 或者 php –i 都可以查看扩展。
其实,xhproh, curl都是php的扩展。
php的扩展图phpinfo()2.【为什么要有扩展?】
1)【效率高】复杂的图像算法,需要写成PHP扩展
2)【操作底层,未支持的技术】1)PHP需要支持一项她还未支持的技术。这通常包括包裹一些现成的C函数库,以便提供PHP接口。 2)有些系统调用不能用PHP直接访问,需要编写成扩展,比如使用Linux下的fork()函数创建一个进程。
3)【商业化,保护源码】想商业话一个应用,但是又不想暴露源代码,就可以编写成扩展。
3.【PHP核心组成?】
Zend引擎:1)核心、基础设施 2)PHP语法实现 3)脚本编译执行 4)扩展机制 5)内存管理
SAPI:服务器抽象层, 上层调用它
php核心构成图二、走向开发(编写扩展)
总体步骤①下载PHP源码
要开发PHP扩展,第一步要下载PHP源代码,因为里面有开发扩展需要的工具。
下载地址: wget http://cn2.php.net/get/php-5.5.38.tar.gz/from/this/mirror
②生成扩展组件框架
ext_skel 命令
./ext_skel --extname=myext 会生成扩展基础文件夹及文件 php-5.5.38/ext/myext/
③修改配置文件
修改config.m4文件,去掉配置前的dnl
config.m4: 这是Unix环境下的Build System配置文件,后面将会通过它生成配置和安装。
修改配置config.m4④编写核心函数(可用默认的)
步骤如下(myext.c):
1、使用宏PHP_FE将函数加入到myext_functions中
2、使用宏PHP_FUNCTION定义函数体
扩展c文件myext.c⑤编译扩展+重启
1、调用phpize程序生成编译配置文件 cd myext && phpize
2、编译扩展库 ./configure --with-php-config=/Data/apps/php/bin/php-config
3、make
4、make test
5、sudo make install 生成myext.so 文件
6、 sudo vim /Data/apps/php/lib/php.ini 加上 extension=myext.so
7、 sudo /Data/apps/php/sbin/php-fpm reload
修改php配置文件,添加扩展三、了解原理
php的生命周期php的变量在内核--写时复制php的变量在内核--底层代码对比php的变量在内核--结构体表示
HashTable分析--结构体HashTable分析--绘图表示Zend引擎API
四、写在最后
php的核心是C语言,核心引擎是Zend,了解HashTable就能了解php的精髓。php的扩展开发说到底是C语言的编写,更见底层功力,希望大家都能成为技术大牛。
最后,预祝大家2018年万事如意,心想事成!