java使用velocity来生成java代码 April 17, 2016 / Java / apacal / 1391 CLICK / 0 comment # java使用velocity来生成java代码 ## What 类似于PHP中的Smarty,Velocity是一个基于java的模板引擎(template engine)。 它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码定义的对象。从而实现界面和Java代码的分离,使得界面设计人员可以和java程序开发人员同步开发一个遵循MVC架构的web站点。 另外,Velocity的能力远不止web站点开发这个领域,例如,它可以从模板(template)产生SQL和PostScript、XML,它也可以被当作一个独立工具来产生源代码和报告,或者作为其他系统的集成组件使用。 Velocity也可以为Turbine web开发架构提供模板服务(template service)。 当然我们也可以使用velocity来动态生成java文件,比如在android开发中根据xml中定义的数据表结构来生成对应的java类。 cpp有对应的ctemplate。 ## How ### 获取Velocity相关JAR文件: 从[http://velocity.apache.org/](http://velocity.apache.org/)网站上下载最新的Velocity,这里我们下载了velocity-1.7.zip 相关Jar包添加到项目中: 解压velocity-1.7.zip,发下其根目录下有两个JAR文件: ~~~ velocity-1.7.jar velocity-1.7-dep.jar ~~~ 其中velocity-1.7-dep.jar包含了: ~~~ velocity-1.7.jar commons-collections-3.2.1.jar commons-lang-2.4.jar oro-2.0.8.jar ~~~ 这些JAR文件位于解压目录的lib目录下, 在JAR包不冲突的情况下可以直接使用velocity-1.7-dep.jar 载类路径下添加velocity.properties文件: 该文件一般包含如下配置: ~~~ runtime.log = /tmp/velocity_example.log file.resource.loader.path = /tmp input.encoding = UTF-8 output.encoding = UTF-8 ~~~ ~~~ runtime.log指定日志文件存放位置 file.resource.loader.path指定模板的加载位置 input.encoding指定输入编码 output.encoding指定输出编码 ~~~ 我们也可以在java中动态设置这些信息, ~~~java try { Velocity.init(); Velocity.setProperty("file.resource.loader.path", templatePath); } catch(Exception e) { System.out.println("Problem initializing Velocity : " + e ); return; } /* lets make a Context and put data into it */ VelocityContext context = new VelocityContext(); context.put("formatter", formatter); context.put("packageName", "cn.apacal.codegen.table"); context.put("tableName", "user"); Map fields = new HashMap<>(); fields.put("type", "int"); fields.put("content", "String"); context.put("fields", fields); ~~~ ### Velocity语法 #### 访问对象属性: 默认是不支持访问对象属性的,可以通过编写方法来变通实现访问属性。 ~~~java public class Field { public String type; public String defaultValue; public String getType() { return type; } public String getDefaultValue() { return defaultValue; } public boolean isNullDefault() { if (defaultValue == null || defaultValue.length() <= 0) { return false; } else { return true; } } @Override public String toString() { return String.format("type[%s], defaultValue[%s]", type, defaultValue); } } ~~~ 在模版中使用调用方法来访问属性,在 1.7版本中,定义Field的类必须是一个单独的文件,内嵌类是不起作用。 ~~~java this field type is ${field.getType()} ~~~ #### 遍历List集合: ~~~ #foreach($element in $list) #element #end ~~~ #### 使用判断语句: ~~~ #if($condition) true #else false #end ~~~ #### 获取迭代索引值: 默认使用变量名:``velocityCount`` 也可以自定义此变量名,在``velocity.properties``中设置: ``directive.foreach.counter.name=index`` 设置索引起始位置为0: ``directive.foreach.counter.initial.value=0`` #### 遍历Map变量: ~~~ #foreach($key in $map.keySet()) $key : $map.get($key) #end ~~~ ####在模板中进行赋值: ~~~ #set(#a=”Hello World!”) $a #set($array1=[1..10]) #foreach($entry in $array1) #entry #end ~~~ ## code-gen-java项目 [code-gen-java](https://github.com/apacal/code-gen.git)是一个使用~~~velocity~~~来生成java源文件的程序,可以用在一些重复性的编码工作,比如根据xml来生成对应的java文件。 在code-gen-java项目中,是一个根据xml文件生成对应的java源码文件。 ### 使用方法 在idea中生成jar文件,然后在终端运行(或者直接在idea中运行) ``java -jar jarFile genType templatePath templateName xmlPath outPath`` ~~~ java -jar out/artifacts/codegen_jar/codegen.jar db /Users/apacalzhong/IdeaProjects/codegen/template/ table.vm /Users/apacalzhong/IdeaProjects/codegen/template/table.db /Users/apacalzhong/IdeaProjects/codegen/build/ ~~~ Continue reading
cpp编写explode函数 May 19, 2015 / C&C++ / apacal / 3083 CLICK / 0 comment # cpp编写explode函数 ## What 在之前使用php的使用,经常使用explode函数,将字符串分割成数组,而c++本身不提供这样一个函数,很简单就实现这样的一个函数。 ## How ### 使用遍历的方式 简单直接对string进行遍历,发现是delim char时,进行判断是否为空,不为空push到vector中 ~~~cpp #include #include #include using namespace std; vector explode(const string& str, const char& ch) { string next; vector result; // For each character in the string for (string::const_iterator it = str.begin(); it != str.end(); it++) { // If we've hit the terminal character if (*it == ch) { // If we have some characters accumulated if (!next.empty()) { // Add them to the result vector result.push_back(next); next.clear(); } } else { // Accumulate the next character into the sequence next += *it; } } if (!next.empty()) result.push_back(next); return result; } int main (int, char const **) { std::string blah = "___this_ is__ th_e str__ing we__ will use__"; std::vector result = explode(blah, '_'); for (size_t i = 0; i < result.size(); i++) { cout << "\"" << result[i] << "\"" << endl; } return 0; } ~~~ 注意该函数,只有explode后不为空才会push_back到vector中。输出结果如下 ~~~cpp "this" " is" " th" "e str" "ing we" " will use" ~~~ ### 使用STL STL中的getline提供了这样一种形式[http://www.cplusplus.com/reference/string/string/getline/?kw=getline](http://www.cplusplus.com/reference/string/string/getline/?kw=getline) ~~~cpp istream& getline (istream& is, string& str, char delim); ~~~ 我们可以巧妙使用getline分割。 ~~~cpp #include #include #include #include using namespace std; vector explode(const string& str, const char& delim) { vector result; stringstream data(str); string line; while(getline(data,line, delim)) { result.push_back(line); // Note: You may get a couple of blank lines } return result; } int main (int, char const **) { std::string blah = "___this_ is__ th_e str__ing we__ will use__"; std::vector result = explode(blah, '_'); for (size_t i = 0; i < result.size(); i++) { cout << "\"" << result[i] << "\"" << endl; } return 0; } ~~~ 使用STL中的方法,有一点需要注意,分割时,不管结果是否为空都会压入result,但是当最后一个元素是空的话,是不会push_back到result中的,输出结果如下。 ~~~cpp "" "" "" "this" " is" "" " th" "e str" "" "ing we" "" " will use" "" ~~~ ## We 其实c++还是很方便的,特别是stl中有很多强大的地方,之前是搞php的,从php转到c++,觉得也没有太大的难度,只是将[http://php.net/manual/en/](http://php.net/manual/en/)换成了[http://www.cplusplus.com/](http://www.cplusplus.com/)。 觉得使用c++重要一点就是善用stl.stl提供的东西和php提供的一些类似,让你脱离去管理内存,只是没有php那么都内置的函数而已,但是这些你都可以简单就写出来。 Continue reading C++
C指针 March 04, 2015 / C&C++ / apacal / 3133 CLICK / 0 comment # C指针 ## What C的指针一直是一个比较容易犯糊涂的地方,但是这也还是C/C++的最大的优势,更底层地操作硬件。有时间把脑子关于指针的知识梳理一遍。 ### 什么是指针 指针是一个指向内存地址的标志。举个例子来说,一条街道是一块内存,街道上的编号就是指针,知道了编号(指针)就能找到这个地方。但是这个比喻也是不那么恰当的,街道一个和另外一个地址存放的东西是很明显的,有的是车库,有的是商店我们到了这个地址就知道这是放什么的,但是指针不是,指针只是告诉我们开始的位置是哪里,没有告诉我们它的范围,如果我们要使用指针就必须知道这个指针存的是什么,是字符串,整数;指针和它所指向的类型是密不可分的。 ### 指针有什么用 指针是直接操作内存地址,所带来的就是高效。在c语言中没有引用这一概念,只有指针,当函数传递值的时候,使用指针会减少性能上的损耗,此外指针还支持一些加减,解引用等操作,对内存直接进行操作。 ### 指针和数组 C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。对于一个数组,我们只能够做两件事:确定数组的大小,以及获得指向该数组下标为0的元素的指针。下标运算实际上是通过指针来进行的。举个例子来说, ~~~cpp do { printf("%c", "0123456789"[n % 10]); n /= 10; } while ( n >= 0 ); ~~~ 上面一段代码是什么意思,首先字符串等于指向第一个字符的指针,接着对指针进行下标运算,也就是对字符数组进行下边运算,就是对n进行反向打印出来,比如是输入n = 1230 输出 0321。 在C中如果函数传递数组会自动退化为指针,所以为了不对数组进行越界访问,必须将数组大小传递进去(但是当传递的是cstring的时候,可以不需要传递char[]的大小,因为我们可以通过~~~ size_t strlen(const char* string)~~~来计算cstring的大小。) ~~~cpp void printUsername(char * users[]) { unsigned int i, size = sizeof( users ); for ( i = 0; i < size; i++ ) { fprintf(stdout, "%s\t", users[i]); } } ~~~ 一个简单的函数,传递一个char * users[] 的参数,将用户的name全部打印出来,问题在于对users进行sizeof运算的时候,所求的是char ** users的大小,并不是users元素的多少,而是指针的大小,上述代码会core dump或者少打印username,这也就是为什么main函数想要传递两个参数了,~~~int main(int argv, char * argc[])~~~ 或者~~~int main(int argv, char ** argc)~~~。 ### Linux kernel中的container_of Linux设备驱动中有使用到根据struct中的成员来获取一个包含这个struct指针的操作 `` type* container_of(pointer, container_type, container_field)`` ~~~cpp #include #include #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #ifdef __compiler_offsetof #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) #else #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif struct c_dev { int num; }; struct i_node { int i; struct c_dev* i_cdev; }; struct my_dev { int count; struct c_dev my_cdev; }; int main() { struct my_dev dev; printf("dev adress :%p, c_dev address %p \n", &dev, &dev.my_cdev); struct i_node node; node.i_cdev = &dev.my_cdev; struct my_dev* dev_container; dev_container = container_of(node.i_cdev, struct my_dev, my_cdev); printf("dev adress :%p, c_dev address %p \n", dev_container, &dev_container->my_cdev); return 0; } ~~~ 在c_dev里面存放了一个int,而i_node结构有一个c_dev\*,my_dev结构有一个c_dev。 实现实例一个i_node,my_dev dev,然后将my_dev中的c_dev的地址给i_node中的c_dev指针,之后我们就可以根据i_node中的c_dev\* 来获取一个包含c_dev\*的my_dev指针。 注意这种不要经常使用,要不然代码很难理解,并且必须pointer的地址必须是container_type里面的成员的地址。container_of就是这个意思~ *modify 2016-01-20 at GuangZhou TIT* Continue reading C
主页改版 January 26, 2015 / PHP / apacal / 2899 CLICK / 0 comment # 主页改版 ## What 从十一月份辞掉实习后,有空就重构自己的博客,现在终于完成了它,对数据库重新设计,UI重新设计,增加Memcache。 以前那个UI太差了,但是毕竟是自己重头写的,舍不得丢掉去使用wordpress,终于完成了现在这个版本,算是告一段落了,也完成了自己的一个心愿。。 现在的UI设计也还过得去,总结一下,要默默去折腾大c/c++了。 ## How ### 前台改进 ##### 数据使用memcache进行缓冲,速度更快了,(评论也加了cache, 所以不能马上生效,评论是3分钟的cache) ##### 重新设计UI, 适配手机和平板(IE6神马我就不知道了) ##### 增加用户注册,重新设计评论模块(三级评论) ##### menu无限分类,无限级子菜单 ##### 增加了标签,可以对文章进行标签分类 ##### 右边的recent post等是根据您当前查看的内容确定的,属于同一类的(同一category)才会显示(首页自然是整个网站的) ##### 用户和注册的背景是随机的,图片来源于[https://www.desktoppr.co/wallpapers](https://www.desktoppr.co/wallpapers), (自己写了小程序把图片存到七牛云存储,背景的url为[http://wallpapers.apacal.cn/cgi](http://wallpapers.apacal.cn/cgi),欢迎使用其作为页面的背景) ##### 代码使用google-code-prettify 语法高亮 ### 后台改进 ##### 使用jstree作为后台的菜单显示,也是无限子菜单和无限分类 ##### 使用ajax进行数据交互 ##### 管理使用bootstrap table ##### 有一个基础的CommonController 对数据的CURD进行封装,基本建立一个表只想要几行代码就可以实现CMS管理该表 ##### 使用ckedtor作为文本编辑器 ##### 使用ckfinder管理上传的照片和文件 ### 使用的插件 #### google font googlefonts是一个让人又爱又恨的服务。爱是因为上面提供了非常多优秀的开源字体,可以免费使用。恨是因为在大天朝局域网里,自从某天之后,google fonts没有一天是正常的。访问速度极慢,或者干脆访问不了。 但是360同步了google的源,可以将google fonts的请求地址切换到360上去,访问速度杠杠的!切换的方式很简单。只需要把原来是fonts.googleapis.com的地方替换成fonts.useso.com即可。 ### Bootstrap [Bootstrap](http://www.bootcss.com/) 作为前台和后台布局框架,兼容性那是杠杠的。 ### sweetalter 一个很美观的[alter js](http://tristanedwards.me/sweetalert)库,前台和后台都使用它,A BEAUTIFUL REPLACEMENT FOR JAVASCRIPT'S "ALERT" ### summernote [summernote](http://summernote.org/)是一款很不错的html在线编辑器,在前台的评论中使用它,本来也想在后台使用的,但是后来发现ckeditor和ckfinder搭配使用更好。Super Simple WYSIWYG Editor on Bootstrap ### jquery jquery 是必须品,对设计一些交互,比如前台的评论的提交,后台就更多了,编辑,删除,修改状态,选择之类等等都是使用jquery实现的。 [w3cschool.cc](http://www.w3cschool.cc/jquery/ )是一个很好的web参考网站 ### ckeditor & ckfinder [ckeditor](http://ckeditor.com/)和ckfinder搭配使用效果很不错,ckeditor作为在线编辑器,ckfinder作为上传图片和文件管理的工具。 ### bootstrap-fileinput 文件上传插件,在前台用户头像那里使用到了。 ### Jcrop 图片裁剪工具,在前台的用户头像模块使用。 ### jstree & jstree-bootstrap-theme [jstree](http://www.jstree.com/) 是一款强大的插件, 在后台的menu以及想要tree的地方(category, cms menu)。 ### kalendar 一个日历插件,在前台的footer中使用 ## We 经过重构这个博客,对js,html, css又有进一步的了解,但是还是个渣渣。期间还写了一个c的fastcgi,对c也算有收获了,特别的malloc, free, void *之类的。这个博客算是完成了一个自己的心愿,最后也借鉴了greencms来弄了一个我的cms的[home page](http://cms.apacal.cn)。剩下时间就静心去学习c了。。 *modify 2016-01-29 at GuangZhou TIT* Continue reading 心得
apacalblog cms 说明 January 26, 2015 / PHP / apacal / 3085 CLICK / 0 comment # apacalblog cms 说明 ## What 经过1个月的时间,终于将我的博客重构了一遍,页面和逻辑都重写了,介绍地址[http://cms.apacal.cn](http://cms.apacal.cn), git 地址:[https://github.com/apacal/apacalblog](https://github.com/apacal/apacalblog). ## How ### 下载 在github上可以找到最新的版本,下载下来,或者git clone 下来 。 ~~~ git clone https://github.com/apacal/apacalblog ~~~ ### 建立和导入数据库 ~~~ cd doc mysql -uroot -p ~~~ ~~~sql create database apacalblog; use apacalblog; source ./apacalblog.sql ~~~ ### 修改配置文件 编辑配置文件,修改mysql的连接密码和memcached的连接密码等 ~~~ vim Application/Common/Conf/config.php ~~~ ~~~ // pdo 'DB_TYPE' => 'pdo', // 数据库类型 'DB_USER' => 'root', // 用户名 'DB_PWD' => 'dev2014', // 密码 'DB_PORT' => 3306, // 端口 'DB_PREFIX' => 'ablg_',// 数据表前缀 'DB_DSN' => 'mysql:host=localhost;dbname=apacalblog;charset=utf8', "LOAD_EXT_FILE" => "MemcahedManager,MemcachedSession", // Memcache设置 'MemCached' => array( 'hostname' => '127.0.0.1', 'port' => '11211', 'isSasl' => false, 'username' => 'apacal', 'passwd' => 'passwd' ), 'NoCachedDie' => true, ~~~ 注意如果你的memcache是有密码的,将isSasl改为true, 并把密码改成您的密码。 ### 配置web服务器 修改nginx或者apache的配置文件 nginx默认是不支持pathinfo模式的,想要让nginx支持pathinfo。可以参考我的这篇文章[安装nginx + php5-fpm + pathinfo](http://apacal.cn/article/40.html#nav-home). ### 其他 后台的图片管理使用了ckfinder插件,在plugin/ckfinder目录中。 结束 安装好了就打开网站去体验一下,注意默认用户密码是demo,用户demo. Continue reading
FastCGI C简明教程 January 11, 2015 / C&C++ / apacal / 1708 CLICK / 0 comment # FastCgi C 简单教程 ## What 前段时间看到bing官网的背景图片感觉不错,就琢磨着自己弄下来做博客页面的背景,google了一下发现一个网站有很多美丽的风景图片,并且有提供api来获取图片。 [https://www.desktoppr.co/api](https://www.desktoppr.co/api), 但是国内的打开很慢,于是就想先下载存到七牛服务器,在博客中使用七牛的资源,就有了一个[wallpapers_web](https://github.com/apacal/wallpapers.git)的项目(使用c来下载图片和上传到七牛服务器,再把记录存到MYSQL) [http://wallpapers.apacal.cn/cgi](http://wallpapers.apacal.cn/cgi)就是获取一张图片背景,可以把该地址使用到css中的background。 ## How ### 安装fcgilib ~~~ sudo apt-get install libfcgi-dev # or wget http://www.fastcgi.com/dist/fcgi.tar.gz tar zxvf fcgi.tar.gz cd fcgi-2.4.1-SNAP-0311112127/ ./configure && make && make install ~~~ 更多关于fastcgi可以查看[http://www.fastcgi.com/](http://www.fastcgi.com/)里面有各种语言的例子。 ### 编写&编译 ~~~cpp #include "fcgi_stdio.h" void main(void) { int count = 0; while(FCGI_Accept() >= 0) printf("Content-type: text/html\r\n" "\r\n" "FastCGI Hello!" "Request number %d running on host %s\n", ++count, getenv("SERVER_NAME")); } ~~~ 编译时加上-lfcgi ~~~ gcc main.c -lfcgi #or gcc main.c -lfcgi -I/usr/local/include/ -L/usr/local/lib/ ~~~ 在本地运行,看是否有结果输出。 ~~~ ┌[apacalzhong☮apacals-iMac]-(/tmp) └> ./a.out Content-type: text/html FastCGI Hello!Request number 1 running on host (null) ~~~ ### 安装spawn-fcgi和配置nginx 使用spawn-fcgi来管理编写的fastcgi,配置nginx将请求以fastcgi协议给spawn-fcgi ~~~ sudo apt-get install spawn-fcgi ~~~ 配置nginx ~~~ server { listen 80; server_name cgi.apacal.cn; location / { fastcgi_pass 127.0.0.1:8000; fastcgi_index index.cgi; fastcgi_param SCRIPT_FILENAME fcgi$fastcgi_script_name; include fastcgi_params; } } ~~~ 使用spawn-fcgi监听nginx配置的端口, ~~~ spawn-fcgi -a 127.0.0.1 -p 8000 -f /data/wallpapers/cgi/wallpapers_cgi ~~~ 打开网址看能否访问 关于让程序不死的方法 太多时候c的程序会突然崩掉core dump,如何让运行的程序不死,有两种方法,第一种使用shell定时去检查进程是否存在。第二种在程序中先fork一个子进程处理业务,父进程wait等待子进程,如果子进程结束继续循环,实现如下。 ~~~cpp int main() { while(1) { // 父进程fork pid = fork(); if (pid == -1) { fprintf(stderr, "fork() error.errno:%d error:%s\n", errno, strerror(errno)); break; } // 子进程进行业务 if (pid == 0) { MYSQL *conn = mysql_create(); while(FCGI_Accept() >= 0) { printf("Content-type: text/html\r\n"); printf("Location:"); query_string = getenv("QUERY_STRING"); if (conn == NULL) { file_name = NULL; } else { file_name = get_file_name(conn); } if (file_name != NULL && strlen(file_name) > 0) { if (is_self_domain(query_string)) { printf("%s%s", self, file_name); } else { printf("%s%s", yun, file_name); } } else { printf("%s%s", self, "demo.jpg"); } printf("\r\n\r\n"); free(file_name); } mysql_delete(conn); } //父进程 wait, 继续循环 if (pid > 0) { pid = wait(&status); } } retrun 0; } ~~~ ## We 使用c fastcgi 自然更加高的性能,但是编写也没有那么容易。。。 我的项目地址https://github.com/apacal/wallpapers_cgi Continue reading CGI
PHPSTORM Xdebug单步调试 November 15, 2014 / PHP / apacal / 3419 CLICK / 0 comment # PHPSTORM Xdebug单步调试 ## What 以前写php都是用vim ,print_r, echo, var_dump来调试验证程序正确与否,从实习用了phpstorm后,发现一些好的ide对效率还是有很大的帮助,本文就来介绍下然后配置好phpstrom+xdebug。 ## How ### 什么是xdebug,它与phpstorm是怎么工作的 xdebug是一款php调试插件,支持远程调试,就是在php文件运行的时候,能通过tcp协议,来发送调试信息到远程端口,ide在收到调试信息的时候,可以向xdebug发送单步运行,中止运行,停止运行等命令。这样就实现了类似vs那样强大的调试功能。 当服务端收到一个xdebug的请求(浏览器发出,通过特定的请求字段或者cookie值来标志),就会主动和请求者的9000端口建立调试连接。(9000端口是在php.ini文件中定义的,也可以是其他端口,关闭ide的监控时,通过抓包可以看出,服务端向客户机的9000发送请求连接,ide发了个rst断开了) ### 安装xdebug和配置xdebug 如果是使用ubuntu的直接使用apt-get 安装 ~~~ (apacal)-(jobs:0)-(~/yEd) (! 1989)-> sudo apt-cache search xdebug | grep php ~~~ 找到xdebug的ini文件,加入监听端口和key等信息,通过 remote_host 和 remort_port 两个参数指定调试客户端位置。对于本地调试缺省即可,如果要远程调试,把参数改到对应接收调试信息的客户端地址即可 ~~~ (apacal)-(jobs:0)-(~/yEd) (! 1995)-> sudo vim /etc/php5/fpm/conf.d/20-xdebug.ini zend_extension=xdebug.so xdebug.idekey="PHPSTORM" xdebug.remote_host=127.0.0.1 xdebug.remote_port=9000 xdebug.remote_enable=on ~~~ 重启php,查看是否有xdebug的信息 ~~~ (apacal)-(jobs:0)-(~/yEd) (! 1998)-> sudo service php5-fpm restart php5-fpm stop/waiting php5-fpm start/running, process 28489 ~~~ ~~~ (apacal)-(jobs:0)-(~/yEd) (! 1999)-> php -i | grep xdebug | grep 9000 xdebug.remote_port => 9000 => 9000 ~~~ PHP的配置好了,下面进行配置ide。 ### 配置phpstorm File>Settings>PHP>Debug 下,找到 Xdebug 部分配置信息,看 Debug port 是否是 9000 (其实就是要和前面服务端php.ini里的remote_port保持一致); File>Settings>PHP>Debug>DBGp Proxy 下,IDE key 随便填(填在php.ini中的xdebug.idekey的值,后面浏览器扩展设置部分也会用到这个key值);Host填你的XDebug所在服务器地址(本地调试那就是 localhost),Port 填你的HTTP应用所在的端口,缺省当然就是 80 ; File>Settings>PHP>Servers 下,可以添加多个需要Debug的主机信息,便于以后快速切换使用; phpstorm有一个电话的按钮,点击绿色表示监控xdebug的连接,红色表示不监控。 服务端和ide都配置好了,那问题来了,php和ide是怎么建立连接,这里一般就是使用浏览器来做这个事情。 ### 浏览器配置 方法一:在请求的url后面加上XDEBUG_SESSION_START=PHPSTORM 表示注册一个xdebug交互,session为PHPSTORM 比如:http://localhost/phpinfo.php?XDEBUG_SESSION_START=PHPSTORM 方法二:安装浏览器的插件 对于Firefox来说,可以安装类似 easy Xdebug (with moveable icon) 这样的扩展来快速激活Debug状态。!注意!有些xdebug相关扩展可能存在不兼容问题导致设置无效,请选择能够正常工作的扩展。一个不行换另一个试试。安装完之后需要设置一下扩展的参数。主要就是 Value of he debug cookie 的值,设置成与前面在DBGp Proxy下设置的IDE key一样即可。 浏览器配置,如果插件不行就使用方法一来。 关于调试的步骤: 激活PHPStorm IDE的 "Start Listen for PHP Debug Connections" 按钮(右上角 Debug 区类似小电话的图标,带红色禁止小圆圈标志Start Listen for PHP Debug Connections表示未激活,变绿Stop Listen for PHP Debug Connections表示在监听中); 在PHPStorm中设置想要的断点; 在浏览器中激活xdebug cookie(如果是Firefox+easy Xdebug扩展的话,点击工具栏的两个图标Toggle xdebug cookie和Toggle Xdebug Profiler cookie,变红表示cookie已成功设置); 在浏览器中访问对应的PHP页面,正常情况下窗口会弹到 PHPStorm IDE中并进入Debug状态; ## We 配置好了,可以告别var_dump的老方法了,对开发效率还是有些提高。 参考:[remote-drupalphp-debugging-xdebug-and-phpstorm](http://randyfay.com/content/remote-drupalphp-debugging-xdebug-and-phpstorm) Continue reading IDEPHP
PHP使用Memcahced和把session放到memecached中 October 09, 2014 / PHP / apacal / 1918 CLICK / 0 comment # PHP使用Memcahced和把session放到memecached中 ## What 前段时间阿里云开放了了免费的128MB的OCS,就申请了一个,把自己网站一些比较常用的数据放到Memcached里,将session也放到Memcached中。 ### 申请阿里云的OCS 在阿里云官方网站登陆,可以看到OCS服务,开通即可,开通后可以知道自己的hostname, username, passwd等。[阿里云](http://www.aliyun.com/) ### 根据说明安装php-memcached 参看[官方的说明](http://help.aliyun.com/view/11108324_13703944.html?spm=5176.7225337.1997284509.12.pY7QvR),安装完测试一下看是否可以连接到OCS ### 编写memcached instance 考虑到memcached instance 可能要在多个模块使用,需要写一个单实例的instance ~~~php // file MemcachedManager.php class MemcachedManager { public static $mySelf = null; public static $memcached = null; public static function getInstance() { if (!(self::$mySelf instanceof self)){ self::$mySelf = new self; } return self::$memcached; } public function __construct() { $config = C('MemCached'); self::$memcached = new Memcached; //声明一个新的memcached链接 self::$memcached->setOption(Memcached::OPT_COMPRESSION, false); //关闭压缩功能 self::$memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true); //使用binary二进制协议 self::$memcached->addServer(isset($config['hostname']) ? $config['hostname'] : 'http://localhost', isset($config['port']) ? $config['port'] : '11211'); //添加OCS实例地址及端口号 if (isset($config['isSasl']) && $config['isSasl'] === true) { self::$memcached->setSaslAuthData(isset($config['username']) ? $config['username'] : 'apacal', isset($config['passwd']) ? $config['passwd'] : 'dev2014'); //设置OCS帐号密码进行鉴权 } } } ~~~ 在这之后就可以使用MemcachedManager::getInstance()函数来获取memcached的instance ### 编写session interface 使用php的``bool session_set_save_handler ( SessionHandlerInterface $sessionhandler [, bool $register_shutdown = true ] )`` 函数来自定义session存储机制 ~~~php // file MemcachedSession.php require_once dirname(__FILE__) ."/MemcachedManager.php"; session_start(); class MemcachedSession implements SessionHandlerInterface { public function open($save_path, $sessionName) { if (MemcachedManager::getInstance()) { return true; } } public function close() { return true; } public function read($id) { return MemcachedManager::getInstance()->get($id); } public function write($id, $data) { return MemcachedManager::getInstance()->set($id, $data, SESSION_TTL ? SESSION_TTL : 60*60) ? true : false; //return MemcachedManager::getInstance()->set($id, $data, 60*60*24) ? true : false; } public function destroy($id) { return MemcachedManager::getInstance()->delete($id); } public function gc($maxlifetime) { // don not need to flush session, memcached has ttl return true; } } try { set_session_hander(new MemcachedSession(), MemcachedManager::getInstance()); }catch(Exception $e) { print_r($e); } function set_session_hander($handler, Memcached $cachedHandler) { if ($cachedHandler->set('hello', 'hello') === true) { if (!session_set_save_handler($handler, true) == true) { //echo "fail"; } } else { $isToDie = C('NoCachedDie'); if ($isToDie === true) { die('cacehed can not connect'); } } } ~~~ 在应用开始前将上面两个文件包含进来,就完成。我是使用ThinkPHP框架的,所以有一些框架的函数,例如``C函数等`` ## We 之前使用过redis,个人体验,觉得redis的内置结构更多,接口也更全,memcache相对弱一点,好了,现在可以对一些数据进行缓存了;可以看看/var/lib/php目录下,有没有session文件产生,验证session是否放到了Memcached中。 Continue reading
PHP的SESSION机制 September 19, 2014 / PHP / apacal / 1391 CLICK / 0 comment 前言 sessoin和cookie是web中会话控制的方法,理解它的原理有助于我们对于跨站或者跨服务器使用唯一的同样的会话。 默认处理session机制session.save_handler = files session启动流程 1.session_start(),它是session机制的开始,它有一定概率开启垃圾回收,因为session是存放在文件中, PHP自身的垃圾回收是无效的,SESSION的回收是删除文件,这个概率是根据php.ini的配置决定的, 但是有的php版本是将 session.gc_probability = 0,这也就是说概率是0,而是通过cron脚本来实现垃圾回收。 session.gc_probability = 1 session.gc_divisor = 1000 session.gc_maxlifetime = 1440//过期时间 默认24分钟 //概率是 session.gc_probability/session.gc_divisor 结果 1/1000, //不建议设置过小,因为session的垃圾回收,是需要检查每个文件是否过期的。 session.save_path = //不同的系统默认不一样,有一种设置是 "N;/path" //这是随机分级存储,这个样的话,垃圾回收将不起作用,需要自己写脚本 2. session会判断当前是否有$_COOKIE[session_name()] session_name()返回保存session_id在COOKIE中的键值, 这个值可以从php.ini找到 session.name = PHPSESSID //默认值PHPSESSID 3. 如果不存session_id时php会生成一个session_id,然后把生成的session_id作为COOKIE的值传递到客户端. 相当于执行了下面COOKIE 操作,(这一步执行了setcookie()操作,COOKIE是在header头中发送的, 这之前是不能有输出的,PHP有另外一个函数 session_regenerate_id() 如果使用这个函数,这之前也是不能有输出的,在http协议中cookie是在正文之前的,所以凡事设置cookie,在这之前都不能进行标准输出。) setcookie(session_name(), session_id(), session.cookie_lifetime,//默认0 session.cookie_path,//默认'/'当前程序跟目录下都有效 session.cookie_domain,//默认为空 ) 4. 如果存在那么session_id = $_COOKIE[session_name]; 然后去session.save_path指定的文件夹里去找名字为'sess_' . session_id()的文件. 读取文件的内容反序列化,然后放到$_SESSION中 为$_SESSION赋值 比如新添加一个值$_SESSION['test'] = 'blah'; 那么这个$_SESSION只会维护在内存中,当脚本执行结束的时候, 才会把$_SESSION的值写入到session_id指定的文件夹中,然后关闭相关资源. 在这个阶段也可以执行更改session_id的操作, 比如销毁一个旧的的session_id,生成一个全新的session_id。一种应对seesion劫持的策略是在用户登陆后更新一个新的session_id. if (isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time() - 42000, '/');//旧session cookie过期 } session_regenerate_id();//这一步会生成新的session_id //session_id()返回的是新的值 写入SESSION操作 在脚本结束的时候会执行SESSION写入操作,把$_SESSION中值写入到session_id命名的文件中,可能已经存在,也可能需要创建新的文件。 销毁SESSION SESSION发出去的COOKIE一般属于即时COOKIE,保存在内存中,当浏览器关闭后,才会过期,假如需要人为强制过期, 比如退出登录,而不是关闭浏览器,那么就需要在代码里销毁SESSION,方法有很多, setcookie(session_name(), session_id(), time() - 8000000);// 1.退出登录前执行 usset($_SESSION);//2.这会删除所有的$_SESSION数据,刷新后,有COOKIE传过来,但是没有数据。 session_destroy();//3.这个作用更彻底,删除$_SESSION 删除session文件,和session_id 当不关闭浏览器的情况下,再次刷新,2和3都会有COOKIE传过来,但是找不到数据 用户自定一session处理机制 用户自定义session处理机制,更加直观 bool session_set_save_handler ( SessionHandlerInterface $sessionhandler [, bool $register_shutdown = true ] ) SessionHandlerInterface is an interface which defines a prototype for creating a custom session handler. In order to pass a custom session handler to session_set_save_handler() using its OOP invocation, the class must implement this interface. 对SessionHandler的解释: open(string $savePath, string $sessionName) open 回调函数类似于类的构造函数, 在会话打开的时候会被调用。 这是自动开始会话或者通过调用 session_start() 手动开始会话 之后第一个被调用的回调函数。 此回调函数操作成功返回 TRUE,反之返回 FALSE。 close() close 回调函数类似于类的析构函数。 在 write 回调函数调用之后调用。 当调用 session_write_close() 函数之后,也会调用 close 回调函数。 此回调函数操作成功返回 TRUE,反之返回 FALSE。 read(string $sessionId) 如果会话中有数据,read 回调函数必须返回将会话数据编码(序列化)后的字符串。 如果会话中没有数据,read 回调函数返回空字符串。 在自动开始会话或者通过调用 session_start() 函数手动开始会话之后,PHP 内部调用 read 回调函数来获取会话数据。 在调用 read 之前,PHP 会调用 open 回调函数。 read 回调返回的序列化之后的字符串格式必须与 write 回调函数保存数据时的格式完全一致。 PHP 会自动反序列化返回的字符串并填充 $_SESSION 超级全局变量。 虽然数据看起来和 serialize() 函数很相似, 但是需要提醒的是,它们是不同的。 请参考: session.serialize_handler。 write(string $sessionId, string $data) 在会话保存数据时会调用 write 回调函数。 此回调函数接收当前会话 ID 以及 $_SESSION 中数据序列化之后的字符串作为参数。 序列化会话数据的过程由 PHP 根据 session.serialize_handler 设定值来完成。 序列化后的数据将和会话 ID 关联在一起进行保存。 当调用 read 回调函数获取数据时,所返回的数据必须要和 传入 write 回调函数的数据完全保持一致。 PHP 会在脚本执行完毕或调用 session_write_close() 函数之后调用此回调函数。 注意,在调用完此回调函数之后,PHP 内部会调用 close 回调函数。 Note: PHP 会在输出流写入完毕并且关闭之后 才调用 write 回调函数, 所以在 write 回调函数中的调试信息不会输出到浏览器中。 如果需要在 write 回调函数中使用调试输出, 建议将调试输出写入到文件。 destroy($sessionId) 当调用 session_destroy() 函数, 或者调用 session_regenerate_id() 函数并且设置 destroy 参数为 TRUE 时, 会调用此回调函数。此回调函数操作成功返回 TRUE,反之返回 FALSE。 gc($lifetime) 为了清理会话中的旧数据,PHP 会不时的调用垃圾收集回调函数。 调用周期由 session.gc_probability 和 session.gc_divisor 参数控制。 传入到此回调函数的 lifetime 参数由 session.gc_maxlifetime 设置。 此回调函数操作成功返回 TRUE,反之返回 FALSE。 结束 理解了其机制,我们可以使用用户自定的session机制来处理跨服务器,跨域名等的会话,比如使用mysql, memcache, redis来存储session. Continue reading
九个PHP中很有用的功能 July 22, 2014 / PHP / apacal / 1216 CLICK / 0 comment 下面是九个PHP中很有用的功能,不知道你用过了吗? 1. 函数的任意数目的参数 你可能知道PHP允许你定义一个默认参数的函数。但你可能并不知道PHP还允许你定义一个完全任意的参数的函数 下面是一个示例向你展示了默认参数的函数: // 两个默认参数的函数 function foo($arg1 = '', $arg2 = '') { echo "arg1: $arg1\n"; echo "arg2: $arg2\n"; } foo('hello','world'); /* 输出: arg1: hello arg2: world */ foo(); /* 输出: arg1: arg2: */ 现在我们来看一看一个不定参数的函数,其使用到了?func_get_args()方法:// 是的,形参列表为空 function foo() { // 取得所有的传入参数的数组 $args = func_get_args(); foreach ($args as $k => $v) { echo "arg".($k+1).": $v\n"; } } foo(); /* 什么也不会输出 */ foo('hello'); /* 输出 arg1: hello */ foo('hello', 'world', 'again'); /* 输出 arg1: hello arg2: world arg3: again */ 2. 使用 Glob() 查找文件 很多PHP的函数都有一个比较长的自解释的函数名,但是,当你看到glob() 的时候,你可能并不知道这个函数是用来干什么的,除非你对它已经很熟悉了。 你可以认为这个函数就好scandir() 一样,其可以用来查找文件。 // 取得所有的后缀为PHP的文件 $files = glob('*.php'); print_r($files); /* 输出: Array ( [0] => phptest.php [1] => pi.php [2] => post_output.php [3] => test.php ) */ 你还可以查找多种后缀名 // 取PHP文件和TXT文件 $files = glob('*.{php,txt}', GLOB_BRACE); print_r($files); /* 输出: Array ( [0] => phptest.php [1] => pi.php [2] => post_output.php [3] => test.php [4] => log.txt [5] => test.txt ) */ 你还可以加上路径: $files = glob('../images/a*.jpg'); print_r($files); /* 输出: Array ( [0] => ../images/apple.jpg [1] => ../images/art.jpg ) */ 如果你想得到绝对路径,你可以调用?realpath() 函数: $files = glob('../images/a*.jpg'); // applies the function to each array element $files = array_map('realpath',$files); print_r($files); /* output looks like: Array ( [0] => C:\wamp\www\images\apple.jpg [1] => C:\wamp\www\images\art.jpg ) */ 3. 内存使用信息 观察你程序的内存使用能够让你更好的优化你的代码。 PHP 是有垃圾回收机制的,而且有一套很复杂的内存管理机制。你可以知道你的脚本所使用的内存情况。要知道当前内存使用情况,你可以使用memory_get_usage() 函数,如果你想知道使用内存的峰值,你可以调用memory_get_peak_usage() 函数。 echo "Initial: ".memory_get_usage()." bytes \n"; /* 输出 Initial: 361400 bytes */ // 使用内存 for ($i = 0; $i < 100000; $i++) { $array []= md5($i); } // 删除一半的内存 for ($i = 0; $i < 100000; $i++) { unset($array[$i]); } echo "Final: ".memory_get_usage()." bytes \n"; /* prints Final: 885912 bytes */ echo "Peak: ".memory_get_peak_usage()." bytes \n"; /* 输出峰值 Peak: 13687072 bytes */ 4. CPU使用信息 使用getrusage() 函数可以让你知道CPU的使用情况。注意,这个功能在Windows下不可用。 print_r(getrusage()); /* 输出 Array ( [ru_oublock] => 0 [ru_inblock] => 0 [ru_msgsnd] => 2 [ru_msgrcv] => 3 [ru_maxrss] => 12692 [ru_ixrss] => 764 [ru_idrss] => 3864 [ru_minflt] => 94 [ru_majflt] => 0 [ru_nsignals] => 1 [ru_nvcsw] => 67 [ru_nivcsw] => 4 [ru_nswap] => 0 [ru_utime.tv_usec] => 0 [ru_utime.tv_sec] => 0 [ru_stime.tv_usec] => 6269 [ru_stime.tv_sec] => 0 ) */ 这个结构看上出很晦涩,除非你对CPU很了解。下面一些解释: ru_oublock: 块输出操作 ru_inblock: 块输入操作 ru_msgsnd: 发送的message ru_msgrcv: 收到的message ru_maxrss: 最大驻留集大小 ru_ixrss: 全部共享内存大小 ru_idrss:全部非共享内存大小ru_minflt: 页回收 ru_majflt: 页失效 ru_nsignals: 收到的信号 ru_nvcsw: 主动上下文切换 ru_nivcsw: 被动上下文切换 ru_nswap: 交换区 ru_utime.tv_usec: 用户态时间 (microseconds)ru_utime.tv_sec: 用户态时间(seconds) ru_stime.tv_usec: 系统内核时间 (microseconds) ru_stime.tv_sec: 系统内核时间(seconds) 要看到你的脚本消耗了多少CPU,我们需要看看“用户态的时间”和“系统内核时间”的值。秒和微秒部分是分别提供的,您可以把微秒值除以100万,并把它添加到秒的值后,可以得到有小数部分的秒数。 // sleep for 3 seconds (non-busy) sleep(3); $data = getrusage(); echo "User time: ". ($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000); echo "System time: ". ($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000); /* 输出 User time: 0.011552 System time: 0 */ sleep是不占用系统时间的,我们可以来看下面的一个例子: // loop 10 million times (busy) for($i=0;$i<10000000;$i++) { } $data = getrusage(); echo "User time: ". ($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000); echo "System time: ". ($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000); /* 输出 User time: 1.424592 System time: 0.004204 */ 这花了大约14秒的CPU时间,几乎所有的都是用户的时间,因为没有系统调用。 系统时间是CPU花费在系统调用上的上执行内核指令的时间。下面是一个例子: $start = microtime(true); // keep calling microtime for about 3 seconds while(microtime(true) - $start < 3) { } $data = getrusage(); echo "User time: ". ($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000); echo "System time: ". ($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000); /* prints User time: 1.088171 System time: 1.675315 */ 我们可以看到上面这个例子更耗CPU。 5. 系统常量 PHP 提供非常有用的系统常量 可以让你得到当前的行号 (__LINE__),文件 (__FILE__),目录 (__DIR__),函数名 (__FUNCTION__),类名(__CLASS__),方法名(__METHOD__) 和名字空间 (__NAMESPACE__),很像C语言。 我们可以以为这些东西主要是用于调试,当也不一定,比如我们可以在include其它文件的时候使用?__FILE__ (当然,你也可以在 PHP 5.3以后使用 __DIR__ ),下面是一个例子。 // this is relative to the loaded script's path // it may cause problems when running scripts from different directories require_once('config/database.php'); // this is always relative to this file's path // no matter where it was included from require_once(dirname(__FILE__) . '/config/database.php'); 下面是使用 __LINE__ 来输出一些debug的信息,这样有助于你调试程序: // some code // ... my_debug("some debug message", __LINE__); /* 输出 Line 4: some debug message */ // some more code // ... my_debug("another debug message", __LINE__); /* 输出 Line 11: another debug message */ function my_debug($msg, $line) { echo "Line $line: $msg\n"; } 6.生成唯一的ID 有很多人使用 md5() 来生成一个唯一的ID,如下所示: // generate unique string echo md5(time() . mt_rand(1,1000000)); 其实,PHP中有一个叫?uniqid() 的函数是专门用来干这个的: // generate unique string echo uniqid(); /* 输出 4bd67c947233e */ // generate another unique string echo uniqid(); /* 输出 4bd67c9472340 */ 可能你会注意到生成出来的ID前几位是一样的,这是因为生成器依赖于系统的时间,这其实是一个非常不错的功能,因为你是很容易为你的这些ID排序的。这点MD5是做不到的。 你还可以加上前缀避免重名: // 前缀 echo uniqid('foo_'); /* 输出 foo_4bd67d6cd8b8f */ // 有更多的熵 echo uniqid('',true); /* 输出 4bd67d6cd8b926.12135106 */ // 都有 echo uniqid('bar_',true); /* 输出 bar_4bd67da367b650.43684647 */ 而且,生成出来的ID会比MD5生成的要短,这会让你节省很多空间。 7. 序列化 你是否会把一个比较复杂的数据结构存到数据库或是文件中,你并不需要自己去写自己的算法。PHP早已为你做好了,其提供了两个函数:serialize() 和 unserialize(): // 一个复杂的数组 $myvar = array( 'hello', 42, array(1,'two'), 'apple' ); // 序列化 $string = serialize($myvar); echo $string; /* 输出 a:4:{i:0;s:5:"hello";i:1;i:42;i:2;a:2:{i:0;i:1;i:1;s:3:"two";}i:3;s:5:"apple";} */ // 反序例化 $newvar = unserialize($string); print_r($newvar); /* 输出 Array ( [0] => hello [1] => 42 [2] => Array ( [0] => 1 [1] => two ) [3] => apple ) */ 这是PHP的原生函数,然而在今天JSON越来越流行,所以在PHP5.2以后,PHP开始支持JSON,你可以使用 json_encode() 和 json_decode() 函数 // a complex array $myvar = array( 'hello', 42, array(1,'two'), 'apple' ); // convert to a string $string = json_encode($myvar); echo $string; /* prints ["hello",42,[1,"two"],"apple"] */ // you can reproduce the original variable $newvar = json_decode($string); print_r($newvar); /* prints Array ( [0] => hello [1] => 42 [2] => Array ( [0] => 1 [1] => two ) [3] => apple ) */ 这看起来更为紧凑一些了,而且还兼容于Javascript和其它语言。但是对于一些非常复杂的数据结构,可能会造成数据丢失。 8. 字符串压缩 当我们说到压缩,我们可能会想到文件压缩,其实,字符串也是可以压缩的。PHP提供了gzcompress() 和 gzuncompress() 函数: $string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ut elit id mi ultricies adipiscing. Nulla facilisi. Praesent pulvinar, sapien vel feugiat vestibulum, nulla dui pretium orci, non ultricies elit lacus quis ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam pretium ullamcorper urna quis iaculis. Etiam ac massa sed turpis tempor luctus. Curabitur sed nibh eu elit mollis congue. Praesent ipsum diam, consectetur vitae ornare a, aliquam a nunc. In id magna pellentesque tellus posuere adipiscing. Sed non mi metus, at lacinia augue. Sed magna nisi, ornare in mollis in, mollis sed nunc. Etiam at justo in leo congue mollis. Nullam in neque eget metus hendrerit scelerisque eu non enim. Ut malesuada lacus eu nulla bibendum id euismod urna sodales. "; $compressed = gzcompress($string); echo "Original size: ". strlen($string)."\n"; /* 输出原始大小 Original size: 800 */ echo "Compressed size: ". strlen($compressed)."\n"; /* 输出压缩后的大小 Compressed size: 418 */ // 解压缩 $original = gzuncompress($compressed); 几乎有50% 压缩比率。同时,你还可以使用gzencode() 和 gzdecode() 函数来压缩,只不用其用了不同的压缩算法。 9. 注册停止函数 有一个函数叫做register_shutdown_function(),可以让你在整个脚本停时前运行代码。让我们看下面的一个示例: // capture the start time $start_time = microtime(true); // do some stuff // ... // display how long the script took echo "execution took: ". (microtime(true) - $start_time). " seconds."; 上面这个示例只不过是用来计算某个函数运行的时间。然后,如果你在函数中间调用exit() 函数,那么你的最后的代码将不会被运行到。并且,如果该脚本在浏览器终止(用户按停止按钮),其也无法被运行。 而当我们使用了register_shutdown_function()后,你的程序就算是在脚本被停止后也会被运行: $start_time = microtime(true); register_shutdown_function('my_shutdown'); // do some stuff // ... function my_shutdown() { global $start_time; echo "execution took: ". (microtime(true) - $start_time). " seconds."; } Continue reading