PHP to Redis interface(Redisent) 的一个bug

原代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case '$':
  if ($reply == '$-1') return null;
  $response = null;
  $read = 0;
  $size = substr($reply, 1);
  if ($size > 0){
  do {
      $block_size = $size - $read;
      if ($block_size > 1024) $block_size = 1024;
      if ($block_size < 1) break;
      $response .= fread($this->__sock, $block_size); //这边fread读取到有可能小于 $block_size ,而$read累加器最终会误以为全部读取完而退出循环
      $read += $block_size;
    } while ($read < $size);
  }
  fread($this->__sock, 2); /* discard crlf */
  break;
原理分析:即使在阻塞(blocking)模式下,fread也在会因为socket缓冲区未达到
期望(超时)的字节数时而直接返回。(艹,经验和教训告诉我们,开源的东西要慎用)
修正后的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case '$':
  if ($reply == '$-1') return null;
  $response = null;
  $block = null;
  $read = 0;
  $size = substr($reply, 1);
  if ($size > 0){
  do {
      $block_size = $size - $read;
      if ($block_size > 1024) $block_size = 1024;
      if ($block_size < 1) break;
      $block = fread($this->__sock, $block_size);
      $response .= $block;
      $block_size = strlen($block);
      $read += $block_size;
    } while ($read < $size);
  }
  fread($this->__sock, 2); /* discard crlf */
  break;
关于fread “BUG”的官方说明 https://bugs.php.net/bug.php?id=51056 I know about fread() returning less data than asked for, however I could not modify this behaviour without passing some kind of value to lower-level read operation, which will call poll() if socket is blocking. When data is already available in buffer, an information should be passed to the lower-level read() to let it know it should not block. The only non-intrusive solution to fix this would be to temporarly pass socket in non-blocking mode if data was found in PHP buffer. Considering any application handling data from network should handle cases when received data is not complete, I believe it was best to return immediatly if data is found and let the application call fread() again rather than trying to workaround this problem with a dirty solution like passing temporarly in non- blocking mode. Another solution would be to add an argument to the internal read call ("do not block") however it would change the API for the internal stream api, and would require the argument to be handled into each stream wrapper.

基于php分布式程序开发问题

一、伸缩性扩展性问题
根据不同系统层次,实现横向扩展设计,可简单地通过增加主机数或处理进程数来扩展;
在前端浏览器层面,可以通过动态调用不同资源实现一定程度分流,如JavaScript、Flash(ActionScript)中加载应用服务器列表,并根据均衡策略选择其中一台服务器进行请求;
在域名解析层面,可以考虑动态DNS解析服务,为跨机房跨区域服务系统部署提供IP地址的映射达到分流目的;
在可使用负载均衡器(本着开源精神只谈软件)提供扩展支持,常见的是网络层的lvs、HAProxy(也可以工作在应用层)等开源产品;
而在应用层上也可以使用nginx负载均衡扩展机制和upstream策略访问多个php应用服务;
而在php应用服务层面,由php-fpm来管理进程,我们可以随时根据需要,通过php-fpm配置来增减php进程数实现伸缩性。

另外,基于任务层面的调度分发机制下还可以使用消息队列、分布式调度中间件(如:gearman)等实现的消息缓冲,由work端运行的php程序主动pull消息做处理,
通过增减work数量来实现任务的处理能力。

二、高性能问题
缓存思路:WEB系统中高性能的一个重要原则是将资源推送到离用户最近的地方,这就是缓存原则,
主要的缓存方式有如下几个层次:浏览器端的资源和数据缓存、CDN缓存、服务端反向代理、应用层内容及数据缓存、存储服务内部缓存、系统及IO缓存、硬件缓存,
充分利用好每一层缓存机制,可以大幅降低服务器并发读请求流量,极高地提升了请求性能以及响应能力。

批处理:PHP中可以运用共享内存,例如大量插入数据库的操作,我们可以先将SQL语句存于共享内存中(可持久),
等若干时间或一定数据量后整合一次性写入数据库,以此来提升处理性能。

高并发:高并发几乎是所有大型系统的问题起因,特别是并发写请求,很多写请求都会从用户端起走完系统的每个环节直到磁盘。而写请求的过程通常还会伴随每个系统环节的一致性、事务、锁等问题,这意味着有些时候的写请求无法并行完成,并在同一时间只能处理一个相同类型写请求,现实中例子如抢购火车票时的余量、线上秒杀的商品库存,这种情况下一定都会在读写两方面的请求上有着很高的数据一致性要求,在实现业务上,由于PHP没有语言级的共享内存支持,所以我们可以使用类似Redis(集群)这样的高性能NoSQL做数据境像,将数据更新的过程大大简化,以直接读写Redis方式,并异步更新数据到数据库,以支持实时的高并发读写。
另外写请求过后通常还会伴随着每个缓存环节的失效更新等额外负担问题,而且写操作对于整个系统的可用性要求也是非常高的,所以写请求的成本很昂贵。

响应性能和连接数:
响应能力和用户体验息息相关,WEB应用在一定条件下连接数和响应能力上却是存在冲突的,
如:为了提高前端整体响应,可以考虑facebook等使用的BigPipe这样的技术;
常见的可以考虑异步加载页面、拆分资源等,如把一张大的图片切分成若干小图片(小图片多少合适也是有讲究的,通常是一个页的整数倍,根据不同环境假设4.5K的图片,那么在网络传输以及磁盘IO性能上可能就和4K完全不一样了),由于是分发且并行请求,理论情况下显示多个小图片要比一张大图片来得快,但是对于服务端而言,并发请求数也增加了, 对于一个繁忙的服务器,处理瞬间大量网络连接请求的成本有时会很高,容易造成WEB Server响应缓慢,甚至超时,反而降低请求响应。

所以很多时候要根据业务类型权衡响应能力和连接数,对响应有高要求的就需要拆分资源,而对服务端处理并发连接要优化的,就需要整合资源(将JS、CSS等多个文件合成一个)。
一些浏览器对于单个域名请求设置了最大连接数,使用多域名可以增加并发请求数;
配置keepalive timeout,根据连续请求数情况,以及用户持续提交请求的间隔,设置合理数值,设置不当的长连接反而会额外增加服务器负载,影响性能;
将静态资源分离到不同域名,避免动态请求上的cookie加到静态资源上;

数据压缩:数据压缩更像一个时间换空间的作法,通过消耗cpu运算性能降低数据发送流量,额外的好处是还能减少系统数据在内存中拷贝的字节数,对于cpu不是瓶颈的场合建议使用。
异步:异步处理是提高耗时服务处理的一个重要思路,使用消息队列、分布式调度中间件实现异步处理,
由于PHP没有语言级的异步机制以及共享内存支持,故在使用第三方服务时要消耗更多的响应时间在网络通信环节,对于使用php-fpm可以考虑fastcgi_finish_request来提高响应。

分布式系统通常使用client-server-worker模型,性能和响应主要消耗在网络通信以及消息的持久化上,
1、网络通信高性能,对于fast-cgi下的php尽量使用长连接
2、消息的持久化,通常使用写磁盘binlog方式实现,因此开销不小,另外可以考虑第三方的共享内存集群来持久化
3、php语言本身运行时都要初始化进程和引擎来执行代码,性能影响很大,永久进程的php daemon运行方式可以复用进程,提升性能

三、容灾及高可用性
负载均衡+keepalived:由于负载均衡器自身存在的单点问题,使用keepalived可以使得服务在意外停止的情况下重启或切换到其它健康的服务,以保证负载均衡正常工作。
可靠性:如动态DNS在一定情况下可能出现负载不均衡的情况;基于不同网络协议层分发请求时,有着不同的特点,如基于4层的负载均衡,无法判断业务类型,由于不同业务的请求消耗的资源很不一样,有可能造成局部的节点压力过大的情况,而基于7层的负载均衡就能很好地解决这个问题,如Nginx等;其次均衡算法本身存在的问题,如使用随机算法时,有可能把多个请求分发到同一节点,而使用轮洵算法,对节点一视同仁,可能处理能力弱的节点就无法满足服务要求,造成负载过高;另外消息传递模式的问题,多数底层负载均衡使用的是push方式,均衡器无法得知节点的处理情况,有可能把消息阻塞在处理节点,造成负载过高,而使用pull方式的负载均衡,节点可以灵活地根据自身处理能力进行拉取,避免了消息阻塞。
容错:负载均衡机制可以在分布式工作节点故障无响应后自动移除,保证可靠性和隔离性,不影响其它正常节点工作。
异地容灾:对于重要系统要考虑异地容灭,分布式系统可支持远程连接和处理,可根据本地和远程服务的优先级均衡分发请求。
平滑处理能力、流控:分布式平台可以对突发流量直到平滑处理的能力,通常使用消息列队方法进行排队,避免处理时造成系统过载、系统资源耗尽引发更严重的问题。在队列层面,当消息滞留过多时增加流控,对消息的提交根据策略予以拒绝。
余量设计: 通常在物理环境做主机备份,在故障时可切换; 在系统层面做进程管理,可随时增减进程数。
另外daemon方式的php在运行一些时间后可能会出现内存泄露等问题,设置一定策略重启机制如处理N个任务后、处理N个小时后,重启进程方式可最大程度避免问题发生。

四、可维护性
易于安装和部署; 管理和监控工具;可配置;
自动管理,可自动面对不同访问压力做出扩展和收缩。
可恢复:对任意服务层的主机做备份机以便于随时切换,对于系统层服务,如php程序等,很多时候运行于daemon方式,可做到进程的平滑重启,以及代码版本的无逢更新。
根据需要灵活地定制功能,而不轻易被系统所限制,如增加一些脚本解析支持。
分布式平台对于非开发人员的访问接入是否足够简单,比如一些通用的访问协议telnet、http等方式都能给第三的调试和测试带来及大方便。
调试工具是开发阶段重要的组成部分,可根据API描述简单地提交数据,并给出结果以及运行时间等参数数据。

五、安全性
对内消息传递时,保证通信仅限于局域网内,在服务程序、系统或防火墙层面限制对公网开放端口。
对外、跨机房远程通信时,保证消息的加密签名有效性和可靠性,加强认证和访问控制,可添加IP限制、时间有效期限制等机制增加异地通信的安全性;
在程序层面要加强数据的边界校验,防止程序运行时的各种数据溢出造成安全性问题,如:在php中可适时增加编码校验、非法字符过滤、数据大小限制、类型和格式校验等。

六、一致性
分布式资源访问竞争:分为平等组和等级组, 平级组实现较为复杂,为网状结构,需要基于消息的投票算法(如Paxos),优点是可用性较高,缺点是性能相对较差;等级组的实现较为简单,主要基于一个协调服务,如PHP中可以使用共享内存实现一个分布式锁,共享内存本身也是一个协调者,缺点也很明显,因为共享内存本身的可用性也要保障。
强一致性,分布式系统的跨主机事务常见的有2PC(两阶段提交)方式,对系统的可用性影响非常大,任何节点的故障都会造成事务失败,同样也会极大影响整体性能,事务要向每个节点进行提交确认以及资源锁定。
弱一致性是一种有效权衡的解决方案,如MySQL中的Master&Slave结构,以及php中使用的缓存或境像,缓存总是和原数据存在一定时间的不一致性,但最终总是会更新到较新的数据,所谓最终一致性,这经常会导致一些问题,所以我们经常会通过一些机制强制实现缓存和原数据的一致性,包括手工清缓存。最后碰到更新一致性的问题时还可以引入version机制等来解决。

现在常见的分布式中间件如:gearman、thrift、ice 等

php_zmq安装时设置PKG_CONFIG_PATH

pkg-config 是一个供程序编译时提供lib参数的工具,在安装完zmq后会生成一个libzmq.pc,默认在/usr/local/zeromq/lib/pkgconfig下。

libzmq.pc文件里面包含了zmq库的所在的路径,以及需要include的头文件路径等等信息。

在安装php_zmq扩展时需要配置

export PKG_CONFIG_PATH=/usr/local/zeromq/lib/pkgconfig  (如果你的libzmq.pc在这个路径下)

 

 

页面展示与后端动态数据分离方案分析

针对文章:http://lajabs.net/?p=442

基于这种模式可以引伸出另一个话题,既然我们将MVC中的M独立成物理接口,那么我们是否可以考虑在VC上动动刀呢?

没什么不可以,页面布局是固定静态HTML生成的,数据展示完全通过AJAX请求。

但是这么做的好处有哪些?太多了!

1、性能大幅提升,只加载动态内容,html布局一次加载完长期缓存;
2、AJAX的用户体验不必多说;
3、开发效率方面,开发上前后端多线同步进行,互不干扰;
4、运维上可以直接针对前后端做优化,不必刻意做动静态分离,直接分布在独立主机,伸缩性更不在话下;
5、代码维护上相互独立,问题快速定位,很多时候代码维护的时间不比开发少;
6、从架构层面限制了开发人员犯错的概率和影响,开发人员更专注于做一件事,自己的错误只影响系统的一部分;
7、功能更清晰,测试更开心,每个接口都是功能的“现实文档”,更容易实现TDD;
8、内容的访问控制更加灵活,对于统计和数据挖掘等更易实现,特别是对付防盗链防采集太方便了;

说了这么多好处,缺点有哪些?

1、搜索引擎很不友好,比如新闻站就伤不起了,当然也会有像京东、TAOBAO这样非请勿扰的主表示蛋定;
(后续研究表明,搜索引擎的优化可以通过制作爬虫专用的页面来解决,而用户所见的信息是搜索引擎不可抓取的,这反而是一个优点。)

2、开发上管理协调的事务增加了,本来一个PHPer可以把整站包办的(美工、前台、PHP、LINUX、MySQL...),现在要拆分为多步,甚至多个人;

对于PHP大型开发框架的看法

PHP从诞生以来就受到广大编程爱好者的喜欢,成为中小站长的好帮手,并培养了大量的PHP编程人员,但是随着PHP的应用越发广泛,很多时候已不限于从事中小网站的应用,一些大型PHP项目也屡见不鲜。

当我们选择PHP开发大型项目时,就不得不考虑开发效率、开发规范、后期维护等问题,这时大家往往会选择一款人们所认可的开发框架,目前所流行的Zend Framework、Yii、Symfony、CodeIgniter、CakePHP等都声称有着开发大型应用的能力。

新框架层出不穷,但当我们真正应用这些框架去实现产品又总是会有各种不同的问题产生:

一、大型框架的背后往往有着较为深厚的结构理论,最熟悉的莫过于MVC、ORM这样耳熟能详的理论术语,还有很多深度面向对象方面的知识,但是真正了解这些的人却为数不多,使得应用门槛急剧攀升;除此大型框架中的应用细节更是纷繁复杂,学习成本也相对较高,这对于原本只是定位中小应用的PHP变得尤为尴尬。

二、PHP做为一门脚本语言,它的运行往往基于宿主进程(如:apache、php-fpm),在单次请求上经历创建进程、初始化环境、编译脚本、运行引擎、输出、资源回收、进程销毁等一系列过程,在编程语言层面综合运行效率上要比编译型语言慢上2-3个数量级,伴随着消耗大量的系统资源,在此基础上我们还要搭建及加载复杂的开发框架更是增加了其运行成本。而在大型应用中从不缺乏特殊需求,有时PHP加大型框架的运行效率就是致命的。

三、大型框架所考虑的因素过多,开发人员在应用时需要额外关注代码以外的细节过多,如:非标准约定、冗长的手册、琐碎的配置、复杂的文件目录结构、难以限制的合理性约束、千姿百态的类库等等,使得大多数程序员开发过程屡糟困惑,提高开发效率也成了空话。

四、最致命的一点,框架作者不断地寻找银弹,试图制作出一个满足所有需求的怪物。大型应用对系统的松散耦合性要求很高,通常不可能在开发层面直接对数据进行操作,见一个简单的SOA模型(附图),数据层和业务层几乎是物理隔离的,而在业务层的开发上只针对数据层提供的服务接口进行访问。从目前的PHP开发框架来说(特别是MVC模型),通常使用ORM来直接对数据库表进行抽象,并直接加以CRUD操作,靠谱的大型应用是不会这做的(或许适合VPS,但大型应用会选择VPS ??)。

总结,PHP大型框架真的还处于很尴尬的地位,但是从另一方面来说,好的PHP大型框架真的是一个值得很多人学习的好榜样,其中蕴涵了大量的设计理念、设计模式、代码优化、语言特性、软件工程等知识体系,融汇php精髓但又远超PHP本身。

最后还是要对这些大型PHP框架说一流行话,“学之者生,用之者死”。

php输入类型的安全控制方法

我们知道php本身是一种弱类型的编程语言,在WEB应用中,输入方式通常是用户的POST/GET等提交方式,而针对用户提交的数据,数据校验就必不可少了。

简单地如一个GET提交用于查询数据库,http://xx.com/action?id=123
我们在校验时只要简单地对提交进行处理:
$id = intval($_GET['id']);

以上即可,这非常简单,但却极其必要,一旦在开发或日后维护中遗漏,轻则数据泄露,重则系统被人控制。

解决的方法一:就是“命名约定”,简单地说,任何的提交都要符合规定,如原本:
http://xx.com/action?id=123

那么提交的id应当命名为:
http://xx.com/action?i_id=123

我们发现id前面多了一个"i_"前缀表达式,这是什么意思呢?i就是表示他是一个int类型的数据,
这在提交后我们可以在自己的开发框架或者输入控制层中统一地校验。

另外,我们还可以定制"d_"双精度型,"b_"布尔型,"a_"数组型,"s_"字符串型等前缀,针对字符串型这样的复杂类型,在输入时通常无法统一地校验,这时我们还可以更加深入地定制规则,如一个例子"s_olyd_name","s_"表示字符串,"olyd_"表示使用olyd这个对象(或函数)进行校验。

对于一些无法变更的提交(如其它原有的提交方式已经无法变更),我们可以定制一个中间层,将
http://xx.com/action?id=123
这种方法转变为
http://xx.com/action?i_id=123

具体方法可以通过配置特定参数(类似URL路由)来解决:
array(
'id' => 'i_id',
....
);

方法二:是基于方法的改进版本,由于在提交大量参数时需要做全部的预先处理,但是有可能一些参数并不是必须的,我们可以考虑只在第一次使用时校验,这个过程我们使用了具体的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class request
{
 private $pars;
 public function get($par)
 {
  // 缓存处理后的参数
  if(isset($this->pars[$par])) return $this->pars[$par];
  switch ($par{0})
  {
   case 'i':
    $val = intval($_REQUEST[$par]);
   break;
   ....
  }
  $this->pars[$par] = $val;
  return $val;
 }
}

// 具体访问
$request = new request();
echo $request->get('i_id');

当然,以上的例子我们可以使用单例来实现全局的访问。

经过以上的规范,可以大大地避免了偶发情况,以及不经意引起的严重安全问题,并在一定程度上提高了开发效率,提高了代码的可维护性。

js引擎(v8)中实现一个多线程(进程)实例

v8是google开发的一个js引擎,其性能非常出色,包括Chrome在内的开源产品都在使用它,同时我们知道Chrome采用的是多进程模式,本文主要是例举一个基于v8的多进程JS编程API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include <vector>
#include "v8.h"

//读取js文件,返回js文件源码
v8::Handle<v8::String> ReadFile(const char* name)
{
    FILE* file = fopen(name, "rb");
    if (file == NULL) return v8::Handle<v8::String>();
    fseek(file, 0, SEEK_END);
    int size = ftell(file);
    rewind(file);
    char* chars = new char[size + 1];
    chars[size] = '';
    for (int i = 0; i < size;)
    {
     int read = fread(&chars[i], 1, size - i, file);
     i += read;
    }
    fclose(file);
    v8::Handle<v8::String> result = v8::String::New(chars, size);
    delete[] chars;
    return result;
}

//执行js源代码,其中有编译和运行两个小步骤,并直接打印结果
void ExecuteString(const v8::Handle<v8::String> &source)
{
    v8::HandleScope handle_scope;
    v8::Handle<v8::Script> script = v8::Script::Compile(source);

    v8::Handle<v8::Value> result = script->Run();
    v8::String::Utf8Value str(result);
    printf("%s\n", *str);
}


//////////////////////////////////////////


//线程模块,分为初始化和运行两个步骤
class Th : public v8::internal::Thread
{
    public:
        Th(const char * js_func_name) : Thread("nameless")
        {
            strcpy(_fname,js_func_name);
        }

        virtual void Run()
        {
            Execute4Thread();
        }
    private:
        void Execute4Thread()
        {
            v8::Isolate* isolate = v8::Isolate::New();
            v8::Isolate::Scope iscope(isolate);
            v8::HandleScope scope;

            v8::Persistent<v8::Context> context = v8::Context::New();
            v8::Context::Scope cscope(context);
            run_proc();
            context.Dispose();
        }

        void run_proc()
        {
            ExecuteString(ReadFile(_fname));
        }

        char _fname[20];
};

/////////////////////////////////////////


// 这个用于从js的参数中获取参数所属的c++对象,并返回对象指针
template<class T>
T* get_cppobj_ptr(v8::Handle<v8::Object> obj)
{
    v8::Handle<v8::External> field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0)) ;
    void* raw_obj_ptr = field->Value() ;
    return static_cast<T*>(raw_obj_ptr);
};


//暴露给js的c++对象,用于创建和运行线程
class Worker
{
    public:
        static v8::Handle<v8::Value> Start(const v8::Arguments & args)
        {
            Worker * worker_ptr =  get_cppobj_ptr<Worker>(args.Holder()) ;
            worker_ptr->StartWorker(args);
        }

        static v8::Handle<v8::Value> Join(const v8::Arguments & args)
        {
            Worker * worker_ptr =  get_cppobj_ptr<Worker>(args.Holder()) ;
            worker_ptr->JoinWorker(args);
        }

        inline void StartWorker(const v8::Arguments & args)
        {
            for (int i = 0; i < args.Length(); i++)
            {
                v8::String::Utf8Value str(args[i]);
                Th *th = new Th(*str);
                th->Start();
                ths_.push_back(th);
            }
        }

        inline void JoinWorker(const v8::Arguments & args)
        {
            std::vector<Th*>::iterator it = ths_.begin();
            for (; it!=ths_.end(); ++it)
            {
                (*it)->Join();
            }
        }
    private:
        std::vector<Th*> ths_;
};


//注册相关的c++对象到js环境中
void creat_obj(v8::Persistent<v8::Context> &exec_context)
{
    v8::Handle<v8::ObjectTemplate> foo_templ ;
    v8::Handle<v8::Object> foo_class_obj ;
    v8::Handle<v8::External> foo_class_ptr ;

    Worker * g_foo_ptr = new Worker;
    foo_templ = v8::ObjectTemplate::New();
    foo_templ->SetInternalFieldCount(1);
    foo_class_obj = foo_templ->NewInstance();

    foo_class_ptr = v8::External::New(static_cast<Worker *>(g_foo_ptr)) ;
    foo_class_obj->SetInternalField(0, foo_class_ptr) ;

    foo_class_obj->Set(v8::String::New("Start"),
                     v8::FunctionTemplate::New(Worker::Start)->GetFunction()) ;

    foo_class_obj->Set(v8::String::New("Join"),
                     v8::FunctionTemplate::New(Worker::Join)->GetFunction()) ;

    exec_context->Global()->Set(v8::String::New("Worker"),
                              foo_class_obj,
                              (v8::PropertyAttribute)(v8::ReadOnly)) ;
}



int main()
{
    v8::HandleScope handle_scope;
    v8::Persistent<v8::Context> exec_context = v8::Context::New();
    v8::Context::Scope context_scope(exec_context);
    creat_obj(exec_context);

    ExecuteString(ReadFile("test.js"));
    return 0;
}

假设我们有三个js脚本文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// test.js
Worker.Start('test1.js');
Worker.Start('test2.js');
Worker.Join();
'ok';


// test1.js
var str = 'test1';
for(i=0;i<=10000;i++)
    str;



// test2.js
var str = 'test2';
str;

这边使用的是linux环境下,v8默认使用pthread库,通过静态编译以上的c++程序,

[root@hx test]# g++ -o test test.cpp libv8.a -I/var/www/cpp/v8/src -lpthread -fno-rtti -fno-exceptions

[root@hx test]# ./test
test2
test1
ok

我们可以看到由于test1的运行时间比较长,故而后输出结果。

v8引擎中的多线程应用

v8引擎为多线程作了很多的准备,包括作用域、上下文切换的特性,但在v8提供的api中并没有提及多线程,于是花了些时间查找源码,有了一些小小的收获。

linux下的v8是封装pthread实现的,其中使用prctl对线程进行命名,线程使用Start()进行初始化,使用Join()启动线程,同时线程会回调对象方法Run()。

贴一个多线程的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <assert.h>
//#include <v8.h>
#include "v8.h" //注意,这边包含的是v8源文件目录里的v8.h

using namespace v8;
void ExecuteString(v8::Handle<v8::String> source)
{
    v8::HandleScope handle_scope;
    v8::Handle<v8::Script> script = v8::Script::Compile(source);
    v8::Handle<v8::Value> result = script->Run();
    v8::String::Utf8Value str(result);
    printf("%s\n", *str);
}


class Th : public v8::internal::Thread
{
    public:
        Th(const char *pname) : Thread(pname),_pname(pname) { }
        void Execute4Thread()
        {
            v8::Isolate* isolate = v8::Isolate::New();
            v8::Isolate::Scope iscope(isolate);
            v8::HandleScope scope;

            v8::Persistent<v8::Context> context = Context::New();
            v8::Context::Scope cscope(context);

            ExecuteString(String::New("'hello.'"));
            context.Dispose();
            //isolate->Dispose();
            printf("Thread name:%s\n.",_pname);
        }

        virtual void Run()
        {
          Execute4Thread();
        }
    private:
        const char * _pname;
};

int main()
{
    Th th1("th1");
    Th th2("th2");
    th1.Start();
    th2.Start();
    th1.Join();
    th2.Join();
    return 0;
}

最后选择静态编译这个代码:
g++ -o test test.cpp /var/www/cpp/v8/libv8.a -I/var/www/cpp/v8/src -lpthread -fno-rtti -fno-exceptions

# ./test
hello.
thread name:th1
hello.
thread name:th2

代码中运行了两个线程,有了这样的尝试,我们就很容易为js提供多线程接口,当然我们还可以控制作用域达到全局变量共享的目的,同时也可以提供一个线程锁mutex,更多精彩还在继续中,我会继续更新。

Js解释器V8引擎嵌入的异常处理

在v8引擎中,通常执行一个Js脚本需要经过编译和运行过程,
由于我们的脚本程序很可能不正确,随时造成过程环节的异常,如下脚本:

test.js:

1
2
3
var str = 'Hello';
str = str + ', World!';
alert(str);

使用以下经典的V8 C++示例代码来执行脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <v8.h>
using namespace v8;

//提取js文件
v8::Handle<v8::String> ReadFile(const char* name)
{
    FILE* file = fopen(name, "rb");
    if (file == NULL) return v8::Handle<v8::String>();
    fseek(file, 0, SEEK_END);
    int size = ftell(file);
    rewind(file);
    char* chars = new char[size + 1];
    chars[size] = '';
    for (int i = 0; i < size;)
    {
     int read = fread(&chars[i], 1, size - i, file);
     i += read;
    }
    fclose(file);
    v8::Handle<v8::String> result = v8::String::New(chars, size);
    delete[] chars;
    return result;
}

int main()
{
    HandleScope handle_scope;
    Handle<Context> context = Context::New();
    Context::Scope context_scope(context);

    const char * fname = "test.js";
    Handle<String> source = ReadFile(fname);
    //ExecuteString(source, String::New(fname), true, true);

    Handle<Script> script = Script::Compile(source);
    Handle<Value> result = script->Run();
    String::AsciiValue ascii(result);
    printf("%s\n", *ascii);
    return 0;
}

然后编译程序
# g++ -o test test.cpp -lv8
# ./test
:58: Uncaught ReferenceError: alert is not defined
Segmentation fault

在执行过程遇到JS语法错误时,V8很干脆地中上了进程,提醒段错误。
在这我们正常应用环境是无法使用的,需要有一种异常处理机制来收集并处理错误。
这就是v8::TryCatch ,在编译前声明,在之后的编译和执行中只要有错误的发生均可捕获。
查找了相关的V8源代码,改进后的代码实现如下(函数体),
其中source为源文件内容,name用来标记当前脚本名称(文件名),
print_result表示是否打印脚本执行结果,report_exceptions表示是否报告异常(错误):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bool ExecuteString(v8::Handle<v8::String> source,
                   v8::Handle<v8::Value> name,
                   bool print_result,
                   bool report_exceptions)
{
  v8::HandleScope handle_scope;
  v8::TryCatch try_catch;   //这里设置异常机制
  v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
  if (script.IsEmpty()) {
    // Print errors that happened during compilation.
    if (report_exceptions)
      ReportException(&try_catch);
    return false;
  } else {
    v8::Handle<v8::Value> result = script->Run();
    if (result.IsEmpty()) {
      assert(try_catch.HasCaught());
      // Print errors that happened during execution.
      if (report_exceptions)
        ReportException(&try_catch);
      return false;
    } else {
      assert(!try_catch.HasCaught());
      if (print_result && !result->IsUndefined()) {
        // If all went well and the result wasn't undefined then print
        // the returned value.
        v8::String::Utf8Value str(result);
        const char* cstr = ToCString(str);
        printf("%s\n", cstr);
      }
      return true;
    }
  }
}

相关的异常处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const char* ToCString(const v8::String::Utf8Value& value) {
  return *value ? *value : "<string conversion failed>";
}


void ReportException(v8::TryCatch* try_catch) {
  v8::HandleScope handle_scope;
  v8::String::Utf8Value exception(try_catch->Exception());
  const char* exception_string = ToCString(exception);
  v8::Handle<v8::Message> message = try_catch->Message();
  if (message.IsEmpty()) {
    // V8 didn't provide any extra information about this error; just
    // print the exception.
    printf("%s\n", exception_string);
  } else {
    // Print (filename):(line number): (message).
    v8::String::Utf8Value filename(message->GetScriptResourceName());
    const char* filename_string = ToCString(filename);
    int linenum = message->GetLineNumber();
    printf("%s:%i: %s\n", filename_string, linenum, exception_string);
    // Print line of source code.
    v8::String::Utf8Value sourceline(message->GetSourceLine());
    const char* sourceline_string = ToCString(sourceline);
    printf("%s\n", sourceline_string);
    // Print wavy underline (GetUnderline is deprecated).
    int start = message->GetStartColumn();
    for (int i = 0; i < start; i++) {
      printf(" ");
    }
    int end = message->GetEndColumn();
    for (int i = start; i < end; i++) {
      printf("^");
    }
    printf("\n");
    v8::String::Utf8Value stack_trace(try_catch->StackTrace());
    if (stack_trace.length() > 0) {
      const char* stack_trace_string = ToCString(stack_trace);
      printf("%s\n", stack_trace_string);
    }
  }
}

最后修改main入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char* argv[])
{
    HandleScope handle_scope;

    Handle<Context> context = Context::New();
    Context::Scope context_scope(context);

    const char * fname = "test.js";
    Handle<String> source = ReadFile(fname);
    ExecuteString(source, String::New(fname), true, true);

    return 0;
}

重新编译后运行结果为:

# ./test
test.js:3: ReferenceError: alert is not defined
alert(str);
^
ReferenceError: alert is not defined
at test.js:3:1

已经可以正常报告错误。

boost.asio防止恶意空连接的方法

网络服务器通常要应对一些意外情况,如空连接行为,指在遇到客户端连接后不进行任何操作,并很可能在大量空连接情况下导致服务器资源耗尽而无法工作。

以下代码主要工作在连接后首次接收客户端消息的环节添加一个timer,并在指定时间后检测是否已接收到消息(验证消息环节此处略去),如果没有收到消息则可认为这是一个非正常连接,并马上断开。timer使用boost::asio::deadline_timer,相对比较简单,详情如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::asio;
using boost::asio::ip::tcp;

class Session : public boost::enable_shared_from_this
{
public:
    Session(boost::asio::io_service& io_service):
    timer_(io_service)
    ,socket_(io_service)
    ,is_read_(false)
    {
        Start();
    }

    void Start()
    {
        socket_.async_read_some
        (
            boost::asio::buffer(buff,1024),
            bind
            (
                &Session::HandleRead,
                shared_from_this(),
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred
            )
        );
        //当客户端连接上5秒钟以后检测是否进行了通信
        timer_.expires_from_now(boost::posix_time::seconds(5));
        timer_.async_wait(boost::bind(&Session::time_out, shared_from_this()));
    }

    void HandleRead(const boost::system::error_code& error, size_t len)
    {
        if (!error)
        {
            is_read_ = true;
            cout << buff << endl;
            socket_.async_read_some
            (
                boost::asio::buffer(buff,1024),
                bind
                (
                    &Session::HandleRead,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred
                )
            );
        }
    }
private:
    void time_out()
    {
        //判断没有从客户端读取到消息的情况下断开连接
        if(!is_read_) socket_.close();
    }

private:
    deadline_timer timer_;
    tcp::socket socket_;
    char buff[1024];
    bool is_read_;
};

无觅相关文章插件,快速提升流量