使用Node.js作为完整的云环境开发堆栈(1)


随着技术创新表面上继续以指数级速度发展,新思想层出不穷。服务器端的 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,如下所示:

  1. lion% node   
  2. > var foo = {bar: ‘baz’};   
  3. > console.log(foo);   
  4. { bar: ‘baz’ }   
  5. > 

在这个例子中,创建了对象foo,然后调用console.log 将它输出到控制台。这十分有效而且有趣,不过当您使用 tab 完成功能来探讨 foo 时,如下面的例子所示,真正的乐趣才刚刚开始。如果输入 foo.bar.,然后按下 tab 键,您将看到对象上的可用方法。

  1. > foo.bar.   
  2. [...output suppressed for space...]   
  3. foo.bar.toUpperCase foo.bar.trim   
  4. foo.bar.trimLeft foo.bar.trimRight 

试用 toUpperCase 方法似乎很有趣,下面显示了它的用法:

  1. > foo.bar.toUpperCase();   
  2.  ‘BAZ’ 

您可以看到,该方法将字符串转换为大写字母。这类交互式开发非常适合于使用像 Node.js这样的事件驱动型框架进行开发。


在完成简单介绍之后,我们开始真正地构建一些东西。


用 Node.js 构建聊天服务器


Node.js 让编写基于事件的网络服务器变得十分简单。例如,让我们创建一些聊天服务器。第一个服务器十分简单,几乎没有什么功能,也没有任何异常处理。


一个聊天服务器允许多个客户端连接到它。每个客户端都可以编写消息,然后广播给所有其他用户。下面给出了最简单的聊天服务器的代码。

  1. net = require(‘net’);   
  2. var sockets = [];   
  3. var s = net.Server(function(socket) {   
  4. sockets.push(socket);   
  5. socket.on(‘data’, function(d) {   
  6. for (var i=0; i < sockets.length; i++ ) {   
  7. sockets[i].write(d);   
  8. }   
  9. });   
  10. });   
  11. s.listen(8001); 

在不到 20 行代码中(实际上,真正实现功能的代码只有 8 行),您已经构建了一个能够使用的聊天服务器。下面是这个简单程序的流程:


◆ 当一个套接字进行连接时,将该套接字对象附加到一个数组。


◆ 当客户端写入它们的连接时,将该数据写到所有的套接字。


现在,让我们检查所有代码,并解释这个例子如何实现聊天服务器预定功能。第一行允许访问 net 模块的内容:

  1. net = require(‘net’); 

让我们使用这个模块中的 Server。


您将需要一个位置来保存所有客户端连接,以便在写入数据时可以写到它们中去。下面是用于保存所有客户端套接字连接的变量:

  1. var sockets = [];  

下一行开始一个代码块,规定当每个客户端连接时要做的事情。

  1. var s = net.Server(function(socket) { 

传递到 Server 中的惟一参数是将针对每个客户端连接进行调用的一个函数。在这个函数中,将客户端连接添加到所有客户端连接的列表中:

  1. sockets.push(socket); 

下一部分代码建立了一个事件处理器,规定了当一个客户端发送数据时要做的事情:

  1. socket.on(‘data’, function(d) {   
  2. for (var i=0; i < sockets.length; i++ ) {   
  3. sockets[i].write(d);   
  4. }   
  5. }); 

socket.on() 方法调用为节点注册一个事件处理器,以便当某些事件发生时它知道如何处理。当接收到来自客户端的数据时,Node.js 会调用这个特殊的事件处理器。其他的事件处理器包括 connect、end、timeout、drain、error 和 close。


socket.on() 方法调用的结构类似于前面提过的 Server() 调用。您传入一个函数给这两者,当有事发生时调用此函数。这种回调方法在异步网络框架中很常见。这是当开始使用像 Node.js 这样的异步框架时,拥有过程编程经验的人会遇到的主要问题。


在这种情况下,当任意客户端发送数据给服务器时,就会调用这个匿名函数并将数据传入函数中。它基于您已经积累的套接字对象列表进行迭代,并给它们全部发送相同的数据。每个客户端连接都将接收到这些数据。


这个聊天服务器十分简单,它缺少一些非常基础的功能,比如识别是谁发送哪条消息,或者处理某个客户端断开的情况。(如果一个客户端从这台聊天服务器断开,任何人发送消息,服务器都会崩溃。)


下面的源代码(在下载示例文件中叫做 chat2.js )是一个经过改进的套接字服务器,其功能有所增强,能够处理“糟糕的情况“(比如客户端断开)。

  1. net = require(‘net’);   
  2. var sockets = [];   
  3. var name_map = new Array();   
  4. var chuck_quotes = [   
  5. "There used to be a street named after Chuck Norris, but it was changed because   
  6. nobody crosses Chuck Norris and lives.",   
  7. "Chuck Norris died 20 years ago, Death just hasn't built up the courage to tell   
  8. him yet.",   
  9.  
  10. "Chuck Norris has already been to Mars; that's why there are no signs of life.",   
  11. "Some magicians can walk on water, Chuck Norris can swim through land.",   
  12. "Chuck Norris and Superman once fought each other on a bet. The loser had to start   
  13. wearing his underwear on the outside of his pants."   
  14. ]   
  15.  
  16.    
  17.  
  18. function get_username(socket) {   
  19. var name = socket.remoteAddress;   
  20. for (var k in name_map) {   
  21. if (name_map[k] == socket) {   
  22. name = k;   
  23. }   
  24. }   
  25. return name;   
  26. }   
  27.  
  28.    
  29.  
  30. function delete_user(socket) {   
  31. var old_name = get_username(socket);   
  32. if (old_name != null) {   
  33. delete(name_map[old_name]);   
  34. }   
  35. }   
  36.  
  37.    
  38.  
  39. function send_to_all(message, from_socket, ignore_header) {   
  40. username = get_username(from_socket);   
  41. for (var i=0; i < sockets.length; i++ ) {   
  42. if (from_socket != sockets[i]) {   
  43. if (ignore_header) {   
  44. send_to_socket(sockets[i], message);   
  45. }   
  46. else {   
  47. send_to_socket(sockets[i], username + ‘: ‘ + message);   
  48. }   
  49. }   
  50. }   
  51. }   
  52.  
  53.    
  54.  
  55. function send_to_socket(socket, message) {   
  56. socket.write(message + ‘\n’);   
  57. }   
  58. function execute_command(socket, command, args) {   
  59. if (command == ‘identify’) {   
  60. delete_user(socket);   
  61. name = args.split(‘ ‘, 1)[0];   
  62. name_map[name] = socket;   
  63. }   
  64. if (command == ‘me’) {  
  65. name = get_username(socket);   
  66. send_to_all(‘**’ + name + ‘** ‘ + args, socket, true);   
  67. }   
  68. if (command == ‘chuck’) {   
  69. var i = Math.floor(Math.random() * chuck_quotes.length);   
  70. send_to_all(chuck_quotes[i], socket, true);   
  71. }   
  72. if (command == ‘who’) {   
  73. send_to_socket(socket, ‘Identified users:’);   
  74. for (var name in name_map) {   
  75. send_to_socket(socket, ‘- ‘ + name);   
  76. }   
  77. }   
  78. }   
  79.  
  80.    
  81.  
  82. function send_private_message(socket, recipient_name, message) {   
  83. to_socket = name_map[recipient_name];   
  84. if (! to_socket) {   
  85. send_to_socket(socket, recipient_name + ‘ is not a valid user’);   
  86. return;   
  87. }   
  88. send_to_socket(to_socket, ‘[ DM ' + get_username(socket) + ' ]: ‘ + message);   
  89. }   
  90.  
  91.    
  92.  
  93. var s = net.Server(function(socket) {   
  94. sockets.push(socket);   
  95. socket.on(‘data’, function(d) {   
  96. ddata = d.toString(‘utf8′).trim();   
  97. // check if it is a command   
  98.  
  99. var cmd_re = /^\/([a-z]+)[ ]*(.*)/g;   
  100. var dm_re = /^@([a-z]+)[ ]+(.*)/g;   
  101. cmd_match = cmd_re.exec(data)   
  102. dm_match = dm_re.exec(data)   
  103. if (cmd_match) {   
  104. var command = cmd_match[1];   
  105. var args = cmd_match[2];   
  106. execute_command(socket, command, args);   
  107. }   
  108. // check if it is a direct message   
  109. else if (dm_match) {   
  110. var recipient = dm_match[1];   
  111. var message = dm_match[2];   
  112. send_private_message(socket, recipient, message);   
  113. }   
  114. // if none of the above, send to all   
  115. else {   
  116. send_to_all(data, socket);   
  117. };   
  118. });   
  119. socket.on(‘close’, function() {   
  120. sockets.splice(sockets.indexOf(socket), 1);   
  121. delete_user(socket);   
  122. });   
  123. });   
  124. s.listen(8001); 




标签:
Warning: Invalid argument supplied for foreach() in C:\zl\webjia\view.php on line 50

友情链接
轻松育儿世界奇观
苏ICP备16066217号-2