随着技术创新表面上继续以指数级速度发展,新思想层出不穷。服务器端的 JavaScript 就是这些新思想之一。 Node.js 是一种事件驱动的 I/O 框架,用于 UNIX 类平台上的 V8 JavaScript 引擎,适合于编写可伸缩的网络程序,如 Web 服务器。 Node.js 正是这种新思想的实现。
51CTO推荐专题:Node.js专区
Node.js 并非与 JavaScript 抗衡,而是使用它作为完整的开发堆栈,从服务器端代码一直延伸到浏览器。Node.js 还充分利用了另一种创新思想:通过回调利用异步 I/O 的并发性模型。
Node.js 云计算平台
在云计算环境中使用 Node.js 框架时,能显示出它的一个巨大优点。对于应用程序开发人员,这往往归结使用平台即服务 (PaaS) 或基础架构即服务 (IaaS) 模型。对于开发人员而言,最抽象和公认最方便的方法是使用 PaaS 提供程序。图 1 十分简单地说明了 PaaS 和IaaS 模型的结构。
图 1. PaaS 与 IaaS 结构

最近,一个激动人心的开源项目 Cloud Foundry 公布了代码以创建一个能够运行 Node.js的私有 PaaS。同样的主机引擎也可用在公共云和商业云中,而且它们接受软件补丁。
广州网站建设,网站建设,广州网页设计,广州网站设计
基础架构管理是一大痛点,如果能够将这项工作外包(永远!)给规模经营的提供商,且无论是源代码,还是物理硬件资源,对于开发人员确实是一个激动人心的时刻。
使用 Node.js shell
在我们着手编写一个完整的 Node.js 例子之前,让我们先开始介绍如何使用交互式 shell。如果尚未安装 Node.js,您可以参考资源部分,然后按照说明安装它,或者使用在线的交互式 Node.js 站点之一,它允许您直接在浏览器中输入代码。
要在 Node.js 中以交互方式编写 JavaScript 函数,在命令行提示中输入node,如下所示:
- lion% node
- > var foo = {bar: ‘baz’};
- > console.log(foo);
- { bar: ‘baz’ }
- >
在这个例子中,创建了对象foo,然后调用console.log 将它输出到控制台。这十分有效而且有趣,不过当您使用 tab 完成功能来探讨 foo 时,如下面的例子所示,真正的乐趣才刚刚开始。如果输入 foo.bar.,然后按下 tab 键,您将看到对象上的可用方法。
- > foo.bar.
- [...output suppressed for space...]
- foo.bar.toUpperCase foo.bar.trim
- foo.bar.trimLeft foo.bar.trimRight
试用 toUpperCase 方法似乎很有趣,下面显示了它的用法:
- > foo.bar.toUpperCase();
- ‘BAZ’
您可以看到,该方法将字符串转换为大写字母。这类交互式开发非常适合于使用像 Node.js这样的事件驱动型框架进行开发。
在完成简单介绍之后,我们开始真正地构建一些东西。
用 Node.js 构建聊天服务器
Node.js 让编写基于事件的网络服务器变得十分简单。例如,让我们创建一些聊天服务器。第一个服务器十分简单,几乎没有什么功能,也没有任何异常处理。
一个聊天服务器允许多个客户端连接到它。每个客户端都可以编写消息,然后广播给所有其他用户。下面给出了最简单的聊天服务器的代码。
广州网站建设,网站建设,广州网页设计,广州网站设计
- net = require(‘net’);
- var sockets = [];
- var s = net.Server(function(socket) {
- sockets.push(socket);
- socket.on(‘data’, function(d) {
- for (var i=0; i < sockets.length; i++ ) {
- sockets[i].write(d);
- }
- });
- });
- s.listen(8001);
在不到 20 行代码中(实际上,真正实现功能的代码只有 8 行),您已经构建了一个能够使用的聊天服务器。下面是这个简单程序的流程:
◆ 当一个套接字进行连接时,将该套接字对象附加到一个数组。
◆ 当客户端写入它们的连接时,将该数据写到所有的套接字。
现在,让我们检查所有代码,并解释这个例子如何实现聊天服务器预定功能。第一行允许访问 net 模块的内容:
- net = require(‘net’);
让我们使用这个模块中的 Server。
您将需要一个位置来保存所有客户端连接,以便在写入数据时可以写到它们中去。下面是用于保存所有客户端套接字连接的变量:
- var sockets = [];
下一行开始一个代码块,规定当每个客户端连接时要做的事情。
- var s = net.Server(function(socket) {
传递到 Server 中的惟一参数是将针对每个客户端连接进行调用的一个函数。在这个函数中,将客户端连接添加到所有客户端连接的列表中:
- sockets.push(socket);
下一部分代码建立了一个事件处理器,规定了当一个客户端发送数据时要做的事情:
- socket.on(‘data’, function(d) {
- for (var i=0; i < sockets.length; i++ ) {
- sockets[i].write(d);
- }
- });
socket.on() 方法调用为节点注册一个事件处理器,以便当某些事件发生时它知道如何处理。当接收到来自客户端的数据时,Node.js 会调用这个特殊的事件处理器。其他的事件处理器包括 connect、end、timeout、drain、error 和 close。
socket.on() 方法调用的结构类似于前面提过的 Server() 调用。您传入一个函数给这两者,当有事发生时调用此函数。这种回调方法在异步网络框架中很常见。这是当开始使用像 Node.js 这样的异步框架时,拥有过程编程经验的人会遇到的主要问题。
在这种情况下,当任意客户端发送数据给服务器时,就会调用这个匿名函数并将数据传入函数中。它基于您已经积累的套接字对象列表进行迭代,并给它们全部发送相同的数据。每个客户端连接都将接收到这些数据。
这个聊天服务器十分简单,它缺少一些非常基础的功能,比如识别是谁发送哪条消息,或者处理某个客户端断开的情况。(如果一个客户端从这台聊天服务器断开,任何人发送消息,服务器都会崩溃。)
下面的源代码(在下载示例文件中叫做 chat2.js )是一个经过改进的套接字服务器,其功能有所增强,能够处理“糟糕的情况“(比如客户端断开)。
- net = require(‘net’);
- var sockets = [];
- var name_map = new Array();
- var chuck_quotes = [
- "There used to be a street named after Chuck Norris, but it was changed because
- nobody crosses Chuck Norris and lives.",
- "Chuck Norris died 20 years ago, Death just hasn't built up the courage to tell
- him yet.",
- "Chuck Norris has already been to Mars; that's why there are no signs of life.",
- "Some magicians can walk on water, Chuck Norris can swim through land.",
- "Chuck Norris and Superman once fought each other on a bet. The loser had to start
- wearing his underwear on the outside of his pants."
- ]
- function get_username(socket) {
- var name = socket.remoteAddress;
- for (var k in name_map) {
- if (name_map[k] == socket) {
- name = k;
- }
- }
- return name;
- }
- function delete_user(socket) {
- var old_name = get_username(socket);
- if (old_name != null) {
- delete(name_map[old_name]);
- }
- }
- function send_to_all(message, from_socket, ignore_header) {
- username = get_username(from_socket);
- for (var i=0; i < sockets.length; i++ ) {
- if (from_socket != sockets[i]) {
- if (ignore_header) {
- send_to_socket(sockets[i], message);
- }
- else {
- send_to_socket(sockets[i], username + ‘: ‘ + message);
- }
- }
- }
- }
- function send_to_socket(socket, message) {
- socket.write(message + ‘\n’);
- }
- function execute_command(socket, command, args) {
- if (command == ‘identify’) {
- delete_user(socket);
- name = args.split(‘ ‘, 1)[0];
- name_map[name] = socket;
- }
- if (command == ‘me’) {
- name = get_username(socket);
- send_to_all(‘**’ + name + ‘** ‘ + args, socket, true);
- }
- if (command == ‘chuck’) {
- var i = Math.floor(Math.random() * chuck_quotes.length);
- send_to_all(chuck_quotes[i], socket, true);
- }
- if (command == ‘who’) {
- send_to_socket(socket, ‘Identified users:’);
- for (var name in name_map) {
- send_to_socket(socket, ‘- ‘ + name);
- }
- }
- }
- function send_private_message(socket, recipient_name, message) {
- to_socket = name_map[recipient_name];
- if (! to_socket) {
- send_to_socket(socket, recipient_name + ‘ is not a valid user’);
- return;
- }
- send_to_socket(to_socket, ‘[ DM ' + get_username(socket) + ' ]: ‘ + message);
- }
- var s = net.Server(function(socket) {
- sockets.push(socket);
- socket.on(‘data’, function(d) {
- ddata = d.toString(‘utf8′).trim();
- // check if it is a command
- var cmd_re = /^\/([a-z]+)[ ]*(.*)/g;
- var dm_re = /^@([a-z]+)[ ]+(.*)/g;
- cmd_match = cmd_re.exec(data)
- dm_match = dm_re.exec(data)
- if (cmd_match) {
- var command = cmd_match[1];
- var args = cmd_match[2];
- execute_command(socket, command, args);
- }
- // check if it is a direct message
- else if (dm_match) {
- var recipient = dm_match[1];
- var message = dm_match[2];
- send_private_message(socket, recipient, message);
- }
- // if none of the above, send to all
- else {
- send_to_all(data, socket);
- };
- });
- socket.on(‘close’, function() {
- sockets.splice(sockets.indexOf(socket), 1);
- delete_user(socket);
- });
- });
- s.listen(8001);



