Node.js

小蝌蚪聊天室

1. 新建项目 创建如下目录与文件: game-server是我们“游戏”的后端服务器目录,负责处理用户的登陆、聊天等相关逻辑。web-server是我们网站的服务器目录。 { "name": "pomelo-todpole-chat", "version": "0.0.1", "private": false, "dependencies": { "pomelo": "1.1.6" } } { "name": "pomelo-todpole-chat", "version": "0.0.1", "private": false, "dependencies": { "express": "4.12" } } cd ./game-server && npm install -d echo '============ game-server npm installed ============' cd .. cd ./web-server && npm install -d echo '============ web-server npm installed ============' 然后在项目根目录下执行命令:sh npm-install.sh 2. 测试pomelo 参考pomelo的官方demo HelloWorld,新建game-server/app.js文件,内容如下: var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', 'pomelo-tadpole-chat'); // app configuration app.configure('production|development', 'connector', function(){ app.set('connectorConfig', { connector : pomelo.connectors.hybridconnector, heartbeat : 3, useDict : true, useProtobuf : true }); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); }); 然后我们尝试运行一下, $cd game-server $pomelo start 结果输出如下: 红色的error提示说没有找到master根servers的配置文件,启动失败。当然我们可以参考官方的HelloWorld demo来添加这两个文件,但我现在还不想这么简单copy,我们来试一下通过调试程序找出错误所在。 3. 使用WebStorm来调试pomelo项目 WebStorm是一个很好用的javascript IDE,用WebStorm打开我们的pomelo-tadpole-chat项目,然后点击上方工具栏的Edit Configuration菜单,添加项目配置。 在弹出的Run/Debug Configuration窗口的左上角,点击➕号,然后新增一个Node.js配置 配置内容如下图,其中Name随便填,至于Application parameters为何要填写"env=development type=all",请参考文章《pomelo start命令的背后》 之后保存该配置,回到WebStorm的主窗口,在这选择我们刚配置好的game-server,然后点击右边的运行按钮。我们会发现WebStorm下方弹出输出窗口: yes!我们在WebStorm上运行pomelo成功。。。地报错了。接下来就可以设置断点进行调试了。 参考文章:使用 WebStorm IDE 调试 Pomelo 应用程序 4. 定位错误 通多设置断点,我们可以追踪到出错的时候的调用过程如下所示:

pomelo start命令的背后-pomelo源码分析(1)

首先,$which pomelo发现全局的pomelo命令指向的是/usr/local/bin/pomelo文件,该文件是/usr/local/lib/node_modules/pomelo/bin/pomelo文件的软链接,直接查看该文件的内容,我们发现这个pomelo其实就是一个js文件。而且头一行为: ,这句话表示告诉系统,我们要调用node进程来执行pomelo这个js脚本文件。$pomelo start = $node /usr/local/bin/pomelo;$pomelo start -D = $node /usr/local/bin/pomelo start -D。 再往下看,有这么一段代码: program.command('start') .description('start the application') .option('-e, --env <env>', 'the used environment', DEFAULT_ENV) .option('-D, --daemon', 'enable the daemon start') .option('-d, --directory, <directory>', 'the code directory', DEFAULT_GAME_SERVER_DIR) .option('-t, --type <server-type>,', 'start server type') .option('-i, --id <server-id>', 'start server id') .action(function(opts) { start(opts); }); 这是Node.js的commander模块的用法,commander模块比Node.js内置的process.argv强大得多,具体用法自己百度。上面这段代码很容易理解,就是对start参数,调用下面这个start(opts)函数。 function start(opts) { var absScript = path.resolve(opts.directory, 'app.js'); // 获得app.js的绝对路径(默认当前路径下app.js) if (!fs.existsSync(absScript)) { abort(SCRIPT_NOT_FOUND); } // 如果不存在app.js文件,打印错误并终止程序 var logDir = path.resolve(opts.directory, 'logs'); // 获得logs目录(默认为当前路径下的logs) if (!fs.existsSync(logDir)) { fs.mkdir(logDir); } // 不存在则新建该目录 var ls; var type = opts.type || constants.RESERVED.ALL; // 默认得到type=all (就是要启动master进程) var params = [absScript, 'env=' + opts.env, 'type=' + type]; // 至此默认会得到params = ['/当前绝对路径/app.js', 'env = development', 'type=all'] if(!!opts.id) { params.push('startId=' + opts.id); } if (opts.daemon) { // 是否启用守护进程模式 ls = spawn(process.execPath, params, {detached: true, stdio: 'ignore'}); // 已守护进程方式生成子进程 ls.unref(); console.log(DAEMON_INFO); process.exit(0); // 父进程退出 } else { ls = spawn(process.execPath, params); // 调用Node.js内置的child_process.spawn()方法生成子进程,并返回子进程对象 // ==> "node /当前绝对路径/app.js env = development type=all" ls.stdout.on('data', function(data) { console.log(data.toString()); }); // 打印将子进程的标准输出 ls.stderr.on('data', function(data) { console.log(data.toString()); }); // 打印子进程的错误输出 } } 最后让我们来实际验证一下上面的结论。在game-server目录下执行$pomelo start,然后再开一种终端检查$ps -ef|grep node,你会发现有如下两个进程: 2093 号进程"node /usr/local/bin/pomelo start" 就是 pomelo start进程,它负责解析pomelo start命令的参数,然后生成2094号子进程"/usr/.../node /Users/.../app.js env=development type=all"。这个子进程就是pomelo服务器进程组中的master服务进程,它又负责生成gate、connector等子服务器进程。

Node.js入门

什么是Node.js 简单来说,Node.js是一个服务器端的javascript运行时环境,使得我们可以在服务器端运行javascript代码。 至于安装Node.js,Node.js官网有已编译的二进制文件,也有源文件包。我自己是下载源文件重新编译安装的。 Hello,World 方法一:直接执行无参数的node命令,进入node命令行界面,打印Hello, World。 方法二:执行node hello.js命令,调用写好的javacript脚本。 console.log('Hello, World'); 一个基本的Web Server 这是nodejs.org的一个官方例子,使用Node.js建立一个web服务器。打开编辑器,建立一个名为app.js的文件,内容如下: 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'); console.log('Server running at http://127.0.0.1:1337/'); 然后运行node app.js命令 打开浏览器访问http://127.0.0.1:1337/ 另外,注意到没,跟之前的hello.js脚本不一样,运行这个app.js脚本后,程序并没有自动退出,而是一直在等待http链接。这是因为在listen函数中创建了事件监听器。。。 Node.js的异步回调 我们写一个读取文件内容的脚本,来展示Node.js的异步回调机制。 var fs = require('fs'); fs.readFile('hello.js', 'utf-8', function(err, data) { if(err) { console.error(err); } else { console.log(data); } }); console.log('readfile end.'); 看到输出没,按照传统思路,程序应该是先读取文件,打印文件内容,最后才打印readfile end。而Node.js的I/O默认是异步式的,当程序需要I/O操作时候,并不阻塞来等待I/O完成,而是将I/O请求交给系统,然后接着运行后续任务,系统执行完I/O操作后会事件的形式通知该程序,触发程序中定义的回调函数。如上面代码中的function(err, data) {...}。 Node.js也有定义阻塞式读取文件的API: var fs = require('fs'); var data = fs.readFileSync('hello.js', 'utf-8'); console.log(data); console.log('readfile end.');   javascript的面向对象 学习Node.js的基础是要学好javascript。以前一直觉得javascript只要会调用jQuery以及各种开源插件就行了,从来没去研究过他们的实现方法,对javacript的高级用法也从来没兴趣研究过。但到了Node.js这里,感觉像duang~的一下,被摔一脸,简直门外汉。先来这里恶补一下javascript比较反人类的面向对象概念。 javascript并不是常规意义上的面向对象,因为它没有class,但却又有对象的概念。像C++这样的面向对象语言,对象是基于类(class)的,一个对象就是类的一个实例。而javascript的对象是基于原型(prototype)的,同一个构造函数创建的对象,都保留一个对原型对象prototype的引用。 原型继承函数util.inherits(subConstructor, baseConstructor) var util = require('util'); function BaseClass() { this.name = 'base'; this.value = 2015; this.sayHi = function() { console.log('hi.'); }; } BaseClass.prototype.sayHello = function() { console.log('hello.'); } function SubClass() { this.name = 'sub'; } SubClass.prototype.talk1 = function() { console.log('------'); } var s1 = new SubClass(); util.inherits(SubClass, BaseClass); SubClass.prototype.talk2 = function() { console.log('++++++'); } var s2 = new SubClass(); s1.talk1(); //s1是用旧的SubClass构造的,所以有talk1方法 //s1.talk2(); //s1是用旧的SubClass构造的,所以没有talk2方法 //s1.sayHello(); //s1是用旧的SubClass构造的,所以没有sayHello方法 //s2.talk1(); //s2是用新的SubClass构造的,所以没有talk1方法 s2.talk2(); //s2是用新的SubClass构造的,所以有talk2方法 s2.sayHello(); //s2是用新的SubClass构造的,继承了BaseClass.sayHello方法 //s2.sayHi(); //util.inherits只继承父亲的prototype,不继承父构造函数内定义的属性与方法 BaseClass.prototype.sayWa = function() { console.log('wa!'); } s2.sayWa(); //BaseClass的prototype在s2的原型链上,所以s2可以获得sayWa方法 刚定义完BaseClass与SubClass以及s1对象时候他们内存空间如下: 调用原型继承函数util.inherits(SubClass, BaseClass),并定义了SubClass.prototype.talk2()方法后的内存空间: 由上图可以看出,util.inherits(SubClass, BaseClass)后,SubClass.prototype.__proto__由之前的基本Object对象变成了BaseClass.prototype,并且为SubClass定义了SubClass.super_属性,该属性指向BaseClass函数。 另外有个很重要的一点,在inherits(SubClass, BaseClass)之前定义的s1对象,它的原型仍然是旧的SubClass,而不是新的继承了BaseClass.prototype的SubClass。原因是因为javascript的对象是引用类型的。s1.__proto__指向对旧的SubClass.prototype的引用,而当inherits继承后,SubClass实际上是新建了一个prototype对象,旧的prototype则游离在内存中,仍被s1引用。而后所有新建的SubClass对象则是引用新的SubClass.prototype。并且新建的s2对象内部构成一个原型链:s2.__proto__(=SubClass.prototype).__proto__(=BaseClass.prototype).__proto__(=Object)。因此s2可以调用到SubClass.prototype上定义的属性和BaseClass.prototype上定义的属性。 下面是util.inherits的源码: exports.inherits = function(ctor, superCtor) { ctor.super_ = superCtor; // 新建属性ctor.super_ 等于superCtor ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); // 新建一个对象,该对象有__proto__与constructor两个属性,并将该对象赋值给ctor.prototype // 其中,该对象的__proto__=superCotr.prototype // 该对象的constructor=ctor,并且constructor不可枚举(enumerable),可以被修改(writable)和删除(configurable) }; util.inherits只是继承了“父类”的原型,并没有继承“父类”构造函数中定义的本地属性。这是因为当我们将函数作为构造函数调用时候,并不会追溯到其“父类”的构造函数中,这点可以参考javascript的面向对象概念这篇博文。那如果我想C++中的那样,构造“子类”对象时候会先调用“父类”构造函数,应该怎么办?这里我们参考一下对Node.js EventEmitter的继承,EventEmitter是Node.js中处理事件、回调的核心模块,很多对象(包括程序员自定义的对象)都要继承EventEmitter。 var util = require('util'); var EventEmitter = require('events').EventEmitter; var Connector = function() { EventEmitter.call(this); //主动调用EventEmitter构造函数,并将当前对象传入(即使用EventEmitter构造函数初始化this当前对象) this.xxx = xxx; //接下来定义Connector自己的本地属性 } util.inherits(Connector, EventEmitter); //原型继承EventEmitter.prototype Connector.prototype.start = function() { ... } 这里用到了javascript Function的call方法,类似的还有apply, bind方法,具体用法自己百度。 exports与require Node.js中,一个文件就是一个模块,在文件中用exports标记模块的接口,然后在外部使用require加载该模块。我们做一个简单的例子 function sayHi() { console.log('hi, this is my module.'); } var myModule = require('./myModule.js'); myModule.sayHi(); 运行node testModule.js,结果出错,myModule对象没有sayHi方法,因为在myModule.js中未将sayHi方法标记为模块的接口。正确的写法应该是: exports.sayHi =…