跳到主要内容

nodejs 扫盲 module.exports 与 exports

· 阅读需 5 分钟

在 nodejs 模块中,如果希望向外暴露出模块中的方法和变量,需要使用 module.exports 或者 exports,但是他们的意义却不同。

module.exports

module 表示该模块对象,在 module 对象中有个 exports 的属性,默认值为空对象;exports 是模块往外暴露方法或者变量的接口,可以暴露变量或者方法:

    var a =  10;
module.exports = a;

var a = 10;
var b = 'hello';
module.exports = {age:a,name:b};

var a = {
name : 'hello',
age: 10
}
module.exports = a;

function a(){
console.log('hello')
}
module.exports = a;

var a = function (){
console.log('hello')
}
module.exports = a;

var a = {
name : 'hello',
getName : function(){
console.log(this.name)
}
}
module.exports = a;

exports

exports 是 module.exports 的一个引用,可以为 exports 添加属性,但不能直接赋值,那样就会失去对 module.exports 的引用,便不会暴露出所的值。

    exports.name = 'hello';

exports.getName = function(){
console.log(this.name)
}

因为是 module.exports 的引用,所以每次为 exports 赋值的时候,就是为 module.exports 赋值。 如果直接为 exports 赋值:

    exports = 'hello';

即改变了 exports 对 module.exports 的引用,所以所赋的值无法通过 module.exports 暴露出去。

exports = module.exports = xxx

可以在很多项目中看到这句,当模块要输出一个非 Object 时(比如一个 Function),可以使用

    module.exports = function () {}

此时 module.exports 被覆盖了,而 exports 还是原来的对像的引用,为了避免在后面的代码中仍然使用 exports.xx = yy 而导致不能正确输出,需要把 exports 变量也重新设置为新的 module.exports 的引用,所以一般习惯写成

    exports = module.exports = xxx

JavaScript 异步编程的 Promise 模式

· 阅读需 25 分钟

作者 崔康

异步模式在 web 编程中变得越来越重要,对于 web 主流语言 Javascript 来说,这种模式实现起来不是很利索,为此,许多 Javascript 库(比如 jQuery 和 Dojo)添加了一种称为 promise 的抽象(有时也称之为 deferred)。通过这些库,开发人员能够在实际编程中使用 promise 模式。IE 官方博客最近发表了一篇 文章,详细讲述了如何使用 XMLHttpRequest2 来实践 promise 模式。我们来了解一下相关的概念和应用。

考虑这样一个例子,某网页存在异步操作(通过 XMLHttpRequest2 或者 WebWorkers)。随着 Web 2.0 技术的深入,浏览器端承受了越来越多的计算压力,所以“并发”具有积极的意义。对于开发人员来说,既要保持页面与用户的交互不受影响,又要协调页面与异步任务的关系,这种非线性执行的编程要求存在适应的困难。先抛开页面交互不谈,我们能够想到对于异步调用需要处理两种结果——成功操作和失败处理。在成功的调用后,我们可能需要把返回的结果用在另一个 Ajax 请求中,这就会出现“函数连环套”的情况(在笔者的另一篇文章《NodeJS 的异步编程风格》中有详细的解释)。这种情况会造成编程的复杂性。看看下面的代码示例(基于 XMLHttpRequest2):

    function searchTwitter(term, onload, onerror) {

var xhr, results, url;
url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
xhr = new XMLHttpRequest();
xhr.open('GET', url, true);

xhr.onload = function (e) {
if (this.status === 200) {
results = JSON.parse(this.responseText);
onload(results);
}
};

xhr.onerror = function (e) {
onerror(e);
};

xhr.send();
}

function handleError(error) {
/* handle the error */
}

function concatResults() {
/* order tweets by date */
}

function loadTweets() {
var container = document.getElementById('container');

searchTwitter('#IE10', function (data1) {
searchTwitter('#IE9', function (data2) {
/* Reshuffle due to date */
var totalResults = concatResults(data1.results, data2.results);
totalResults.forEach(function (tweet) {
var el = document.createElement('li');
el.innerText = tweet.text;
container.appendChild(el);
});
}, handleError);
}, handleError);
}

上面的代码其功能是获取 Twitter 中 hashtag 为 IE10 和 IE9 的内容并在页面中显示出来。这种嵌套的回调函数难以理解,开发人员需要仔细分析哪些代码用于应用的业务逻辑,而哪些代码处理异步函数调用的,代码结构支离破碎。错误处理也分解了,我们需要在各个地方检测错误的发生并作出相应的处理。

为了降低异步编程的复杂性,开发人员一直寻找简便的方法来处理异步操作。其中一种处理模式称为 promise,它代表了一种可能会长时间运行而且不一定必须完整的操作的结果。这种模式不会阻塞和等待长时间的操作完成,而是返回一个代表了承诺的(promised)结果的对象。

考虑这样一个例子,页面代码需要访问第三方的 API,网络延迟可能会造成响应时间较长,在这种情况下,采用异步编程不会影响整个页面与用户的交互。promise 模式通常会实现一种称为 then 的方法,用来注册状态变化时对应的回调函数。比如下面的代码示例:

    searchTwitter(term).then(filterResults).then(displayResults);

promise 模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已完成(resolved)和拒绝(rejected)。以 CommonJS Promise/A 标准为例,promise 对象上的 then 方法负责添加针对已完成和拒绝状态下的处理函数。then 方法会返回另一个 promise 对象,以便于形成 promise 管道,这种返回 promise 对象的方式能够支持开发人员把异步操作串联起来,如 then(resolvedHandler, rejectedHandler); 。resolvedHandler 回调函数在 promise 对象进入完成状态时会触发,并传递结果;rejectedHandler 函数会在拒绝状态下调用。

有了 promise 模式,我们可以重新实现上面的 Twitter 示例。为了更好的理解实现方法,我们尝试着从零开始构建一个 promise 模式的框架。首先需要一些对象来存储 promise。

    var Promise = function () {
/* initialize promise */
};

接下来,定义 then 方法,接受两个参数用于处理完成和拒绝状态。

Promise.prototype.then = function (onResolved, onRejected) {
/* invoke handlers based upon state transition */
};

同时还需要两个方法来执行理从未完成到已完成和从未完成到拒绝的状态转变。

    Promise.prototype.resolve = function (value) {
/* move from unfulfilled to resolved */
};

Promise.prototype.reject = function (error) {
/* move from unfulfilled to rejected */
};

现在搭建了一个promise的架子,我们可以继续上面的示例,假设只获取IE10的内容。创建一个方法来发送Ajax请求并将其封装在promise中。这个promise对象分别在xhr.onload和xhr.onerror中指定了完成和拒绝状态的转变过程,请注意searchTwitter函数返回的正是promise对象。然后,在loadTweets中,使用then方法设置完成和拒绝状态对应的回调函数。

    function searchTwitter(term) {

var url, xhr, results, promise;
url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
promise = new Promise();
xhr = new XMLHttpRequest();
xhr.open('GET', url, true);

xhr.onload = function (e) {
if (this.status === 200) {
results = JSON.parse(this.responseText);
promise.resolve(results);
}
};

xhr.onerror = function (e) {
promise.reject(e);
};

xhr.send();
return promise;
}

function loadTweets() {
var container = document.getElementById('container');
searchTwitter('#IE10').then(function (data) {
data.results.forEach(function (tweet) {
var el = document.createElement('li');
el.innerText = tweet.text;
container.appendChild(el);
});
}, handleError);
}

到目前为止,我们可以把promise模式应用于单个Ajax请求,似乎还体现不出promise的优势来。下面来看看多个Ajax请求的并发协作。此时,我们需要另一个方法when来存储准备调用的promise对象。一旦某个promise从未完成状态转化为完成或者拒绝状态,then方法里对应的处理函数就会被调用。when方法在需要等待所有操作都完成的时候至关重要。

    Promise.when = function () {
/* handle promises arguments and queue each */
};

以刚才获取IE10和IE9两块内容的场景为例,我们可以这样来写代码:

    var container, promise1, promise2;
container = document.getElementById('container');
promise1 = searchTwitter('#IE10');
promise2 = searchTwitter('#IE9');
Promise.when(promise1, promise2).then(function (data1, data2) {

/* Reshuffle due to date */
var totalResults = concatResults(data1.results, data2.results);
totalResults.forEach(function (tweet) {
var el = document.createElement('li');
el.innerText = tweet.text;
container.appendChild(el);
});
}, handleError);

分析上面的代码可知,when函数会等待两个promise对象的状态发生变化再做具体的处理。在实际的Promise库中,when函数有很多变种,比如 when.some()、when.all()、when.any()等,读者从函数名字中大概能猜出几分意思来,详细的说明可以参考CommonJS的一个promise实现when.js

除了CommonJS,其他主流的Javascript框架如jQuery、Dojo等都存在自己的promise实现。开发人员应该好好利用这种模式来降低异步编程的复杂性。我们选取Dojo为例,看一看它的实现有什么异同。

Dojo框架里实现promise模式的对象是Deferred,该对象也有then函数用于处理完成和拒绝状态并支持串联,同时还有resolve和reject,功能如之前所述。下面的代码完成了Twitter的场景:

    function searchTwitter(term) {

var url, xhr, results, def;
url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
def = new dojo.Deferred();
xhr = new XMLHttpRequest();
xhr.open('GET', url, true);

xhr.onload = function (e) {
if (this.status === 200) {
results = JSON.parse(this.responseText);
def.resolve(results);
}
};

xhr.onerror = function (e) {
def.reject(e);
};

xhr.send();
return def;
}

dojo.ready(function () {
var container = dojo.byId('container');
searchTwitter('#IE10').then(function (data) {
data.results.forEach(function (tweet) {
dojo.create('li', {
innerHTML: tweet.text
}, container);
});
});
});

不仅如此,类似dojo.xhrGet方法返回的即是dojo.Deferred对象,所以无须自己包装promise模式。

   var deferred = dojo.xhrGet({
url: "search.json",
handleAs: "json"
});

deferred.then(function (data) {
/* handle results */
}, function (error) {
/* handle error */
});

除此之外,Dojo 还引入了 dojo.DeferredList, 支持开发人员同时处理多个 dojo.Deferred 对象,这其实就是上面所提到的 when 方法的另一种表现形式。

    dojo.require("dojo.DeferredList");
dojo.ready(function () {
var container, def1, def2, defs;
container = dojo.byId('container');
def1 = searchTwitter('#IE10');
def2 = searchTwitter('#IE9');

defs = new dojo.DeferredList([def1, def2]);

defs.then(function (data) {
// Handle exceptions
if (!results[0][0] || !results[1][0]) {
dojo.create("li", {
innerHTML: 'an error occurred'
}, container);
return;
}
var totalResults = concatResults(data[0][1].results, data[1][1].results);

totalResults.forEach(function (tweet) {
dojo.create("li", {
innerHTML: tweet.text
}, container);
});
});
});

上面的代码比较清楚,不再详述。

说到这里,读者可能已经对 promise 模式有了一个比较完整的了解,异步编程会变得越来越重要,在这种情况下,我们需要找到办法来降低复杂度,promise 模式就是一个很好的例子,它的风格比较人性化,而且主流的 JS 框架提供了自己的实现。所以在编程实践中,开发人员应该尝试这种便捷的编程技巧。需要注意的是,promise 模式的使用需要恰当地设置 promise 对象,在对应的事件中调用状态转换函数,并且在最后返回 promise 对象。

技术社区对异步编程的关注也在升温,国内社区也发出了自己的声音。资深技术专家老赵就发布了一套开源的异步开发辅助库 Jscex,它的设计很巧妙,抛弃了回调函数的编程方式,采用一种“线性编码、异步执行”的思想,感兴趣的读者可以查看 这里

不仅仅是前端的 JS 库,如今火热的 NodeJS 平台也出现了许多第三方的 promise 模块,具体的清单可以访问 这里

注:本文中的所有代码示例均来自于 IE 官方博客。

原文来自 InfoQ :http://www.infoq.com/cn/news/2011/09/js-promise/

让你的时间格式更加人性化 (Javascript 版)

· 阅读需 5 分钟

这里分享下怎么样将时间显示的更加人性化。

通常我们在页面上显示日期时间的时候,通常是 yyyy-MM-dd HH:mm:ss,也就是年月日时分秒的格式,这样确实比较规范,但是这样的时间在社交类或博客类网站中是否有好处呢?

答案也不是一定的,也许有人说我就喜欢这样的格式!

但是,在数据爆发的这个时代,我们总会想看一些最新的消息,比如:

当我们百度搜某个新闻的时候,我们通常会在结果中查找比较新的,后面的人性化时间会给我们指导作用。

举个例子:

2014 年感动中国十大人物 刚刚

2014 年感动中国十大人物出炉 3 分钟前

看看 2014 年感动中国十大人物 2014-12-27 12:28

这几个结果中,相信一般的老百姓大都会从第一个开始点吧,不多说废话了,看看 Javascript 代码如何实现的:

    function hommizationTime(dateTimeStamp){
var result;
var minute = 1000 * 60;
var hour = minute * 60;
var day = hour * 24;
var month = day * 30;
var now = new Date().getTime();
var diffValue = now - dateTimeStamp;
if(diffValue < 0){
// 非法操作
//alert(" 结束日期不能小于开始日期!");
}
var monthC =diffValue/month;
var weekC =diffValue/(7*day);
var dayC =diffValue/day;
var hourC =diffValue/hour;
var minC =diffValue/minute;

if(monthC>=1){
result="" + parseInt(monthC) + " 个月前发表 ";
}
else if(weekC>=1){
result="" + parseInt(weekC) + " 个星期前发表 ";
}
else if(dayC>=1){
result=""+ parseInt(dayC) +" 天前发表 ";
}
else if(hourC>=1){
result=""+ parseInt(hourC) +" 个小时前发表 ";
}
else if(minC>=1){
result=""+ parseInt(minC) +" 分钟前发表 ";
}else
result=" 刚刚发表 ";
return result;
}

原理就是根据不同的时间单位用当前和发表时的时间差来相除,看,是不是 so easy!

nodejs 模块加载机制的测试结果

· 阅读需 7 分钟

关于上篇看到 InfoQ 和 cnblog 上的博文后,做了个简单的测试。

建立 global 目录和 test 目录以及启动的 index.js,global 中用 module.exports 导出一个只有 a 属性并且 a 属性的值为一个空对象的 json 对象。

    module.exports = {
a : {}
};

然后在根目录的 index.js 中引入这个模块,只需要 require('./global')即可,由于默认会在包下找到 index.js 作为入口。

引入后打印出此对象,然后为 a 属性中的 json 对象追加一个键值对 a=111, 再打印此时,对象中的值已经与开始导出时不同,如果每次加载模块时都直接加载的话那么在其他模块加载肯定与当前不同。

根目录的 index.js 代码

    var context = require('./global');
var test = require('./test');
console.log(context.a);
context.a['a'] = 111;
console.log(" 当前 context 内容 ");
console.log(context.a);
console.log(" 从其他模块引入的 global");
test.getContext();

test 包中的 index.js 代码

    var context = require('../global');
exports.getContext = function(){
console.log(context.a);
};

执行后的结果,如下:

{} 当前 context 内容 { a: 111 } 从其他模块引入的 global { a: 111 }

由此可见,我们在改变了 global 模块中的值的时候,后续在其他模块引用这个模块,它没有直接加载这个模块,而是从 require 的 cache 中读取的缓存的内容。

这样的话,我在之前文章 《nodejs 通讯在工作中的思考和改进》 中提到的全局变量的问题,可以通过封装一个 global 模块来实现全局变量了。

不过我突然怀疑了一下,如果在 test 模块中改变下 global 中的值,这边是否也改变了呢,修改了下 test 中的代码,如下:

    var context = require('../global');
exports.getContext = function(){
console.log(context.a);
};
exports.changeContext = function(){
context.a['b'] = 222;
};

看看是否是引用的相同对象,虽然这理论上就是这样,不过亲手证实了比较放心。

根目录的 index 中如下:

    var context = require('./global');
var test = require('./test');
console.log(" 初始值 ");
console.log(context.a);
context.a['a'] = 111;
console.log(" 当前 context 内容 ");
console.log(context.a);
console.log(" 从其他模块引入的 global");
test.getContext();
console.log(" 在 test 模块中改变 global 中 a 添加属性 b 值 ");
test.changeContext();
console.log(" 改变后的值 ");
console.log(context.a);

再看看结果:

初始值

当前 context 内容 { a: 111 } 从其他模块引入的 global { a: 111 } 在 test 模块中改变 global 中 a 添加属性 b 值 改变后的值 { a: 111, b: 222 }

恩,答案令人满意,确实是在缓存中共享的对象,这样就可以放心的封装为 global 模块了(可能在项目中叫做 applicationContext 比较贴切)。

nodejs 中的模块加载机制笔记

· 阅读需 5 分钟

由于嫌项目中用到的 global 对象不太好,所以想将全局变量封装到自定义的 global 模块中,记得模块是有闭包特性的。

但是我的疑问是,比如我的 global 变量是一个变的,我在某个 js 中引入模块,那么我在另一个地方引入是不是重新初始化了呢。

InfoQ 和博客园找到了些答案,记下笔记。

模块载入策略

Node.js 的模块分为两类,一类为原生(核心)模块,一类为文件模块。原生模块在 Node.js 源代码编译的时候编译进了二进制执行文件,加载的速度最快。另一类文件模块是动态加载的,加载速度比原生模块慢。但是 Node.js 对原生模块和文件模块都进行了缓存,于是在第二次 require 时,是不会有重复开销的。其中原生模块都被定义在 lib 这个目录下面,文件模块则不定性。

原文地址:http://www.infoq.com/cn/articles/nodejs-module-mechanism/

在《深入浅出 nodejs》有这样一段(有部分增减):

1、nodejs 引入模块分四个步骤

路径分析 文件定位 编译执行 加入内存 2、核心模块部分在 node 源代码的编译过程中就编译成了二级制文件,在 node 启动时就直接加载如内存,所以这部分模块引入时,前三步省略,直接加入。

3、nodejs 的模块加载和浏览器 js 加载一样都有缓存机制,不同的是,浏览器仅仅缓存文件,而 nodejs 缓存的是编译和执行后的对象(缓存内存)。

原文地址:http://www.cnblogs.com/tyhmj/p/3799039.html

等测试后再发表评论~

nodejs 通讯在工作中的思考和改进

· 阅读需 20 分钟

这篇文章也算是 nodejs 通讯在工作中的思考和改进吧,记录和分享下期中的体会和思路。

从最先开始的一个光 Socket Server,到现在的 Socket+websocket,其中经过了不少的变化与调整。

阶段一

最开始,紧紧是一个用于接收远端设备传送数据的 Socket 服务器,在这个阶段的代码也是很乱的,基本上所有的实现是以流水账的形式来走了一遍,基本可以满足了现有的需求,由于数据是按照了环保局的 212 通信协议来约定发送的,也让解析的阻力减小了不少,但是从总体代码来看,问题还是挺多的。

稍微列举下我能想到的:

复用性

软件写出来的目的就是为了尽可能的复用,从而简化了工作,从这方面一想,噢,我这时候实现的数据解析基本就是根据当前解析的一些特定数据来实现的,那么问题来了。

如果在这基础之上,又加入了新的数据,怎么办?找目前的情况来看,绝壁是要额外添加实现一遍解析的过程,那肯定是不好的。

扩展性

就像我上面说到的,整个过程都是耦合度很高的,相互都不能剥离,一步接着一步,这样的话,以后想在现有的通讯之上进行扩展,那是相当的麻烦。

分布式(横向扩展)

可能很多设计的比较好的系统来说,他们的瓶颈可能只是服务器的硬件或者是环境因素导致,这个时候只是额外的加入节点就可以解决性能问题,如果是这样的话,那就是最好的。

就我目前的项目来说,都还没往这方面考虑。

阶段二

然后,我第一次比较大的改动是,首先将各个功能划分的更细,分成了一些所谓的“功能模块”出来,database、handler、receive、messagequeue 等等这些。

是的,确实清晰多了,看上去好像各有各的分工。

差不多这时候也就是到了第二个项目,仍然是采用 nodejs 作为通讯服务器,沿用之前的架子吧,这次优化了一些东东:

  1. 项目中能做成配置文件的东西尽量不直接硬编码到代码中。
  2. 加入了一些日志调试信息。
  3. 所有日志打印的一些调试信息都被封装到一个消息字典里,而且还很自大的取了个名字叫 lang_zh_CN,哈哈,就算没有国际化的水平也要有一颗国际化的心嘛(万一实现了呢)。
  4. 解析部分,我是根据了 212 协议中各个区段的特点大概的封装了一下将数据用正则表达式的方式直接就替换+组装转化成 JSON 字符串,我想这应该比之前先循环的切分,再一个个组装到对象中要来的快(总体来说就是个嵌套循环) ,我老早就想优化,只是因为之前那个版本原本是从 java 的原始版本复制成的 nodejs 版本,所以并未做很大的改动,另一方面也是项目催的急根本没时间考虑这些个因素,可能前面的项目中的通讯会成为以个历史遗留问题吧,可能以后在系统的设计上要考虑到将原先的系统接入进来的可能性了。

阶段三

恩,再有就是现在的通讯加入了另一方面的需求,就是在 J2EE 的 web 系统中通过调用通讯端的接口来实现反控现场设备,那么这就涉及到了 HTTP 这一块。

由于之前我自己嫌那个破 Socket Server 太坑爹,每次看有没有错误都要连到服务器去把日志翻出来看,所以我一不爽就给他加入了个 web server(基于 express),上面显示当前服务器的连接数啊、接收数据包成功 / 失败数啊、入库的成功 / 失败数啊、错误数据的数据包内容列表啊、还有个 highchart 画的曲线图包括了包速率和丢包率(其实灵感来自于阿里云的控制台)。

这个设备反控的实现跟上面的 webserver 还是有点关系的,我由于做了周期性的统计当前数据接收入库等情况,而且显示到页面上来,难道我要不停的去刷新查询?

我有这么勤快么,我自己都不信,然后我就想起来之前的一个模块叫 socket.io 的,咋一看名字还以为是 socket,其实啊,它是 websocket 实现(HTML5 中出现的双工通信协议),然后一看,居然 IE8 也有 ajax 的封装实现,不错,就它了,于是就愉快的推送起服务端的数据通信状态了。

回到设备反控,也就是在 J2EE 系统这边引用了我的 socket.io 的客户端 js 进行实时的交互,请求我的 websocket Server,然后我在内部找到对应的 TCP socket 客户端实例进行指令的拼装和发送,接收到回应及数据之后再实时的返回到 J2EE 的页面上。

这里面主要是用到了 nodejs 中的事件发射监听机制 EventEimtter(其实我也不知道搞个事件机制在这到底有用没用),也是怕可能有不同的实现,所以封装了个基本的 Listener 里面用的 event.on()、event.emit() 和 event.removeListener() 这三个基本的,其他的具体以 prototype+call 方式来继承他的操作并加以各自的实现。

哦对,在这过程中也是了解到了 socket.io 的频道的机制,在这里我是用它来广播当前选定的设备所进行的操作,但是它返回的数据只返回给当前控制它的客户端,为此,我还在这上面捣鼓了半天的锁机制,也算是个很简单的实现吧,根据返回数据包中有结束标志的包来释放,核心思想就是不让多人同时操作,但可以看到这台设备在进行什么操作。

我给经理打了个比喻,在一个房间里,有一台电脑(设备),同时只能有一个人在用(反控),其他人必须要等他用完了才能用,这期间就只能看着他用。

差不多现在就是这个程度,我还是不太满意现在的整体实现,可能有下面几个方面:

  1. 如果我需要不单一个端口也就是多个 server,加入其它业务的通信,那么这个怎么设计会将开发成本以及扩展性做到最优。
  2. 目前由于要让一些连接的缓存起来或者配置信息方便使用等,用到了不少全局的变量,然后分布在各个模块,并且各个模块间有一定的耦合性,这种问题要怎么解决。
  3. 再就是考虑到以后可能横向扩展成分布式,前期应该怎么来设计。

这几个问题是我想的比较多而且不是特别擅长的,希望如果有看到想和我交流或者大神有过设计经验的还望不吝赐教。

我的邮箱 yjk99@qq.com

socket.io 认证模块 socketio-auth

· 阅读需 4 分钟

该模块提供了钩子,实现了 socket.io 认证。无需使用查询字符串发送凭据,这不是一个良好的安全习惯。

默认情况下它通过标记客户的未认证的并监听的认证事件。如果客户端提供了错误的凭据或不验证它就会断开。当服务器等待连接的客户端进行身份验证,也不会发射任何事件。

用法

要为 socket.io 连接设置身份验证,只需将服务器套接字和一个配置对象传给 socketio-auth:

    var io = require('socket.io').listen(app);

require('socketio-auth')(io, {
authenticate: authenticate,
postAuthenticate: postAuthenticate,
timeout: 1000
});

所支持的参数是:

authenticate:唯一需要的参数。这是一个函数,它接受客户端发送的数据,并调用一个回调表明,如果认证是成功的:

    function authenticate(data, callback) {
var username = data.username;
var password = data.password;

db.findUser('User', {username:username}, function(err, user) {
if (err || !user) return callback(new Error("User not found"));
return callback(null, user.password == password);
}
}

postAuthenticate:在客户端进行身份验证后才能调用的一个函数。对保持用户和客户端 socket 之间的联系是非常有用的:

    function postAuthenticate(socket, data) {
var username = data.username;

db.findUser('User', {username:username}, function(err, user) {
socket.client.user = user;
}
}

timeout:客户端断开连接之前进行身份验证等待的毫秒数。默认值是 1000。 客户端只需要确保在连接后进行验证:

    var socket = io.connect('http://localhost');
socket.on('connect', function(){
socket.emit('authentication', {client: "John", password: "secret"});
});

该服务器将发射的已认证事件,以确认认证。

作者 Github:socketio-auth

关于在环境监控中用到的 node.js 通讯

· 阅读需 20 分钟

关于环境在线监控这类项目,应该属于物联网范畴了(所谓物联网,在这里的意思大概就是将实实在在的硬件和物联网衔接了起来)。

其实我对物联网的概念也没有太深入的理解,毕竟才接触不到三个月吧,所了解到的就是,基本物联网这块的系统,软件是建立在硬件的基础之上的。

大致分为这么 3 个层次:

1)基础硬件,即用于生产监测数据的监测仪器。

2)通讯模块,这个大块被分成了现场和服务器两边,也就是说,通讯模块是衔接检测仪器和软件系统的桥。

3)软件系统,这块就是为了集中的监控和管理各个监测点的大致情况了。

讲的比较浅显,其实每个层次里都有很多高深的技术。

当然,这通讯里又会有国家或者自己规定的通讯协议。所谓协议,以前,我以为是什么类似于语法检查之类的东西,模模糊糊的。接触了大概就知道,这也就是为了标准化,或者是安全又或者是方便处理,从而双方约定的一种格式,比如环境在线监测中所用到的,212 传输协议,就是国家环保局制定的标准,其中规定了,一个数据包中比如前面必须要带两个#作为包头,然后接下来 4 位是后面整个数据段的长度 ,比如有 233 个字长,那么就是 0233,像这样的等等。

而我要说的是,在监控项目中通讯端中用到的一个 nodejs 与 redis 以及 mongodb。

大致是这样的,因为监测点设备分布在各个地方,数据要上传到我们所谓的数据中心,即软件系统这边,那么大概的关系就可以简化为一个多对一的关系,其实通讯并不一定是单个,可以是个集群什么的,暂且当做一个整体吧。

我来之前,其实通讯端是有了一个版本的,使用的是 java 和 ibm 的 MQ 来实现的,会启动两个进程,一个是接收端,用于接收现场发来的数据,在进行了初步加工之后,即通过 212 协议规定的内容,把数据包中的数据都解析出来,并已 json 格式的字符串存储到 mq(message queue 消息队列)中。另一个进程就是处理端,相反的,它是不断的从消息队列中取出数据,然后进行报警处理等过程后进行入库操作,然后这些数据后续管理就交给运营系统了。

而我当时选择用 nodejs 来重构的一个原因是,我们组的老大说,他们以前那个通讯程序经常挂,这是很不友好的,这样一来数据都等不到保障。

我只是简答的测试了一下,我在本地不断的开启线程去连接我本地的 java 的 socket server,不知道为什么,连到那么几百个的时候就出现连接拒绝的情况。知道的大虾望指教。

而我简单的写了个 nodejs 的 socket server 连接了上千个连接也没有什么异常出现。

可能是我自己机器的原因吧,java 作为老牌的语言,不应该这么脆。

但是作为通讯的话,感觉 java 似乎并不是特别的有优势,毕竟像这种工作大多数都是丢给 C/C++来做,具体原因吧,没研究过 C/C++我还是不讨论他了。

可能恰好把,nodejs 是事件机制,非阻塞模型,正好适合这种无脑疯狂 I/O(密集型 I/O)的场景,而 java 的那块做一些同步、多线程、密集型计算的还是要强大的多的。

说说我写的大概的结构吧:

现在是整在一个项目中的,因为还是前期做了一些简单的测试,都没上线测试,不过压力我觉得应该还是够了,1 天 1 千 4 百万的数据都没问题,我的同事告诉我说,他们一个月的数据似乎才有一千万的数量级。

1. 接收端

** 2. 消息队列**

** 3. 处理端**

这个是分成了三大块。

1. 接收端 顾名思义,就是接受数据用的,做了一些预处理,将数据包转成了 json 串,存入消息队列,至于预处理,这其中就需要协议的支撑了。

考虑的问题:

1)一个是,我需要怎么样把协议抽象成为配置式的,而不需要每次新增一种协议,我就必须要重新写一套处理流程

2)这也是泉哥提醒我的一个问题,粘包断包问题,其实这个问题,有协议应该处理起来还是难度相对不大的,有一次网络阻塞的时候确实出现了数据包也阻塞的问题,当时正在测试协议中的 16 位 CRC 校验算法,结果网络一阻塞,导致我数据包基本都被丢掉了。其实也是不可轻视的一个问题。

3)还有数据包到达的问题,这个的话,现在有一个机制就是应答,在收到某个设备的数据包之后会回发设备的编号,但是我并不知道这里面有什么作用,难道现场机还记录了上传成功率么?

4)还有一个问题是,我们现在并没有对通讯端 server 本身进行状态的监控,我怎么知道现在某时刻他的各方面参数,比如传输率、连接数、连接状态等等。

5)还有一个就是断线重连的问题。

2. 消息队列

这一部分,其实我也并没有做过实际的测试,使用的是 redis 中的 list 结构当做消息队列,这么做起因只是看到网上有关于 nodejs+redis 做消息队列的帖子,而 ibm 的 mq 对于 nodejs 应该是没有支持的。

而且 redis 的表现应该也不会很差,毕竟是个内存数据库,难道还会慢到哪去,他只管存,就行了。

3. 处理端

这部分,我个人对自己写的相当不满意,因为我在找不到很高效的办法的情况下,使用了 javascript 中的 setTimeout 函数来以 0 毫秒延迟循环执行取出操作,总觉得无限循环像是同步操作,不知道各位大神能否指点下。

考虑的问题:

1)其实我觉得直接就在接收到数据的时候处理之后直接处理之后入库 mongodb,会不会快一点,但是回头又想了想,这样的话是不是就回牺牲了写入的效率,而且,不进行读写分离的话,以后不方便扩展,同时在单服务器上写入和读取肯定效率会大大降低。所以暂时还是使用的消息队列在中间过程做为缓冲,当时尝试过在接收到数据包处理后马上通过事件发射机制发送给监听器来处理,但是这直接被老大否决了,所以也没有做测试。

大概的通讯端结构写完了,有什么好的建议或者疑问可以提给我或者留言讨论,也可以发我邮箱 yjk99@qq.com