来源:自学PHP网 时间:2014-12-14 21:15 作者: 阅读:次
[导读] 这篇文章主要介绍了使用Node.js实现一个简单的FastCGI服务器实例,也可以作为一个比较详细的Node.js服务器创建教程,需要的朋友可以参考下...
本文是我最近对Node.js学习过程中产生的一个想法,提出来和大家一起探讨。 Node.js的HTTP服务器 使用Node.js可以非常容易的实现一个http服务,最简的例子如官方网站的示例: 复制代码 代码如下: var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); 这样就快速的搭建了一个监听在1337端口所有http请求的web服务。 但是,在真正的生产环境中,我们一般很少直接使用Node.js作为面向用户的最前端web服务器,原因主要有以下几种: 1.基于Node.js单线程特性的原因,其健壮性的保证对开发人员要求比较高。 所以,使用Node.js作为web服务更多可能是作为游戏服务器接口等类似场景,大多是处理不需用户直接访问且仅作数据交换的服务。 基于Nginx作为前端机的Node.js web服务 基于上述原因,如果是使用Node.js搭建的网站形的产品,常规的使用方式是在Node.js的web服务前端放置另一个成熟的http服务器,如最常使用的是Nginx。 复制代码 代码如下: server{ listen 80; server_name yekai.me; root /home/andy/wwwroot/yekai; location / { location ~ \.(gif|jpg|png|swf|ico|css|js)$ { 这样就比较好的解决了上面提出的几个问题。 使用FastCGI协议通讯 不过,上述代理的方式也有一些不是很好的地方。 什么是FastCGI 快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。 FastCGI产生的背景是用来作为cgi web应用的替代方案,一个最明显的特点是一个FastCGI服务进程可以用来处理一连串的请求,web服务器会把环境变量和这个页面请求通过一个socket比如FastCGI进程与web服务器连接起来,连接可用Unix Domain Socket或是一个TCP/IP连接。关于更多的背景知识可以参考Wikipedia的词条。 Node.js的FastCGI实现 那么理论上我们只需要使用Node.js创建一个FastCGI进程,再指定Nginx的监听请求发送到这个进程就行了。由于Nginx和Node.js都是基于事件驱动的服务模型,“理论”上应该是天作地合的解决方案。下面我们就亲自实现一下。 复制代码 代码如下: ... location / { fastcgi_pass unix:/tmp/node_fcgi.sock; } ... 新建一个文件node_fcgi.js,内容如下: 复制代码 代码如下: var net = require('net'); var server = net.createServer(); server.on('connection', function(sock){ sock.on('data', function(data){
node node_fcgi.js 复制代码 代码如下: connection < Buffer 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 01 87 01...> 这就证明我们的理论基础已经实现了第一步,接下来只需要搞清楚这个buffer的内容如何解析就行了。
FastCGI记录由一个定长前缀后跟可变数量的内容和填充字节组成。记录结构如下: 复制代码 代码如下: typedef struct { unsigned char version; unsigned char type; unsigned char requestIdB1; unsigned char requestIdB0; unsigned char contentLengthB1; unsigned char contentLengthB0; unsigned char paddingLength; unsigned char reserved; unsigned char contentData[contentLength]; unsigned char paddingData[paddingLength]; } FCGI_Record; version :FastCGI协议版本,现在默认就用1就好 具体的结构和说明请参考官网文档(http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3.3)。
似乎好像很简单,就是这样解析一次拿到数据就行了。不过,这里有一个坑,那就是这里定义的是数据单元(记录)的结构,并不是整个buffer的结构,整个buffer由一个记录一个记录这样的组成。一开始可能对于我们习惯了前端开发的同学不大好理解,但是这是理解FastCGI协议的基础,后面还会看到更多例子。 复制代码 代码如下: function getRcds(data, cb){ var rcds = [], start = 0, length = data.length; return function (){ if(start >= length){ cb && cb(rcds); rcds = null; return; } var end = start + 8, header = data.slice(start, end), version = header[0], type = header[1], requestId = (header[2] << 8) + header[3], contentLength = (header[4] << 8) + header[5], paddingLength = header[6]; start = end + contentLength + paddingLength; var body = contentLength ? data.slice(end, contentLength) : null; return arguments.callee(); 注意这里只是简单处理,如果有上传文件等复杂情况这个函数不适应,为了最简演示就先简便处理了。同时,也忽略了requestId参数,如果是多路复用的情况下不能忽略,并且处理会需要复杂得多。 复制代码 代码如下: #define FCGI_BEGIN_REQUEST 1 #define FCGI_ABORT_REQUEST 2 #define FCGI_END_REQUEST 3 #define FCGI_PARAMS 4 #define FCGI_STDIN 5 #define FCGI_STDOUT 6 #define FCGI_STDERR 7 #define FCGI_DATA 8 #define FCGI_GET_VALUES 9 #define FCGI_GET_VALUES_RESULT 10 #define FCGI_UNKNOWN_TYPE 11 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) 接下来就可以根据记录的type来解析拿到真正的数据,下面我只拿最常用的FCGI_PARAMS、FCGI_GET_VALUES、FCGI_GET_VALUES_RESULT来说明,好在他们的解析方式是一致的。其他type记录的解析有自己不同的规则,可以参考规范的定义实现,我这里就不细说了。 复制代码 代码如下: typedef struct { unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */ unsigned char nameLengthB2; unsigned char nameLengthB1; unsigned char nameLengthB0; unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */ unsigned char nameData[nameLength ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0]; unsigned char valueData[valueLength]; } FCGI_NameValuePair41; 对应的实现js方法示例: 复制代码 代码如下: function parseParams(body){ var j = 0, params = {}, length = body.length; while(j < length){ var name, value, nameLength, valueLength; if(body[j] >> 7 == 1){ nameLength = ((body[j++] & 0x7f) << 24) + (body[j++] << 16) + (body[j++] << 8) + body[j++]; } else { nameLength = body[j++]; } if(body[j] >> 7 == 1){ var ret = body.asciiSlice(j, j + nameLength + valueLength); j += (nameLength + valueLength); 这样就实现了一个简单可获取各种参数和环境变量的方法。完善前面的代码,演示我们如何获取客户端ip: 复制代码 代码如下: sock.on('data', function(data){ getRcds(data, function(rcds){ for(var i = 0, l = rcds.length; i < l; i++){ var bodyData = rcds[i], type = bodyData[0], body = bodyData[1]; if(body && (type === TYPES.FCGI_PARAMS || type === TYPES.FCGI_GET_VALUES || type === TYPES.FCGI_GET_VALUES_RESULT)){ var params = parseParams(body); console.log(params.REMOTE_ADDR); } } })(); } 到现在我们已经了解了FastCGI请求部分的基础,下面接着将响应部分的实现,并最终完成一个简单的echo应答服务。 响应部分 响应部分相对比较简单,最简单的情况只需要发送两个记录就行了,那就是FCGI_STDOUT和FCGI_END_REQUEST。 复制代码 代码如下: var res = (function(){ var MaxLength = Math.pow(2, 16); function buffer0(len){ function writeStdout(data){ rcdStdoutHd[0] = 1; return Buffer.concat([rcdStdoutHd, data, buffer0(paddingLength)]); function writeHttpHead(){ function writeHttpBody(bodyStr){ function writeEnd(){ return function(data){ 在最简单的情况下,这样就可以发送一个完整的响应了。把我们最终的代码修改一下: 复制代码 代码如下: var visitors = 0; server.on('connection', function(sock){ visitors++; sock.on('data', function(data){ ... var querys = querystring.parse(params.QUERY_STRING); var ret = res('欢迎你,' + (querys.name || '亲爱的朋友') + '!你是本站第' + visitors + '位用户哦~'); sock.write(ret); ret = null; sock.end(); ... }); 打开浏览器访问:http://domain/?name=yekai,可看到类似“欢迎你,yekai!你是本站第7位用户哦~”。 至此,我们就成功的使用Node.js实现了一个最简单的FastCGI服务。如果需要作为真正的服务使用,接下来只需要对照协议规范完善我们的逻辑就行了。
最后,我们需要考虑的问题是这个方案具体是否具有可行性?可能已经有同学看出了问题,我先把简单的压测结果放上来: 复制代码 代码如下: //FastCGI方式: 500 clients, running 10 sec. Speed=27678 pages/min, 63277 bytes/sec. Requests: 3295 susceed, 1318 failed. 500 clients, running 20 sec. //proxy方式: 500 clients, running 20 sec. //直接访问Node.js服务方式: 500 clients, running 20 sec. 为什么proxy方式反而会优于FastCGI方式呢?那是因为在proxy方案下后端服务是直接由Node.js原生模块跑的,而FastCGI方案是我们自己使用JavaScrip实现的。不过,也可以看出两者方案效率上并没有很大的差距(当然,这里对比的只是简单的情况,如果在真正的业务场景下,差距应该会更大),并且如果Node.js原生支持FastCGI服务,那么效率上应该会更优。 后记 如果有兴趣继续玩的同学可以查看我本文实现的例子源码,这两天研究下了协议规范,其实不难。 |
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习
京ICP备14009008号-1@版权所有www.zixuephp.com
网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com