学习 node.js 第八天:Socket 通讯

前言

一般来讲,HTTP 是基于文本的“单向”通讯机制。这里所谓的“单向”,乃相对于“双向”而言,因为 HTTP 服务器只需根据请求返还恰当的 HTML 给客户端即可,不涉及客户端向服务端的通讯。这种单向的机制比较简单,对网络质量要求也不高。而更多的场景则是需要可靠、稳定的端到端连接。一般这种服务是实时的、有态的而且是长连接,长连接则暗示两段须达致相向通讯的能力,也就说是服务端客户端两者间能够实时地相互间通信。毫无疑问,能够实时通信的服务器正是我们对服务器基本要求之一。区别于 HTTP 服务器以 HTTP 为通讯协议, 实时服务器一般采用较为底层的 TCP/IP 为协议通讯,实现了“套字节 Socket”的双向机制。

Socket 是根据博克莱(U.C.Berkley)大学早期发展的 Socket 概念写成的,其设计理念是是将网络传输类比成文件的读取与写入 (传送的动作被视为是写入/接收的动作被视为是读取),如此、传送与接收就简化为编程人员比较容易懂的 读取与写入,降低了网络编程的学习困难度。

聊天室服务器

聊天室的实时连接基于底层的 TCP 直接连接,为此我们须调用 node 的 TCP 模块。如果不太熟悉所谓 TCP 网络编程?太底层了是不是?没关系,我也不熟悉,边学边做嘛,只不过千万不必因为遇到陌生的词汇而害怕,其实这样原理并不深奥,而且下面的例子也十分的简单易懂!咱们就从最简单的开始吧,下面代码仅仅十行,它的作用是服务器向客户端输出一段文本,完成 Sever --> Client 的单向通讯。

//  Sever --> Client 的单向通讯
var net = require('net');

var chatServer = net.createServer();

chatServer.on('connection', function(client) {
  client.write('Hi!\n'); // 服务端向客户端输出信息,使用 write() 方法
  client.write('Bye!\n');

  client.end(); // 服务端结束该次会话
});

chatServer.listen(9000);

客户端可以是系统自带的 telnet:

telnet 127.0.0.1 9000

执行 telnet 后,与服务点连接,反馈 Hi! Bye! 的字符,并立刻结束服务端程序终止连接。如果我们要服务端接到到客户端的信息?可以监听 server.data 事件并且不要中止连接(否则会立刻结束无法接受来自客户端的消息):

// 在前者的基础上,实现 Client --> Sever 的通讯,如此一来便是双向通讯
var net = require('net');
var chatServer = net.createServer(),    
    clientList = [];
    
chatServer.on('connection', function(client) {
  // JS 可以为对象自由添加属性。这里我们添加一个 name 的自定义属性,用于表示哪个客户端(客户端的地址+端口为依据)
  client.name = client.remoteAddress + ':' + client.remotePort;  
  client.write('Hi ' + client.name + '!\n');  
  clientList.push(client);  
  client.on('data', function(data) {    
     broadcast(data, client);// 接受来自客户端的信息  
  });
});
function broadcast(message, client) {  
    for(var i=0;i<clientList.length;i+=1) {    
      if(client !== clientList[i]) {      
        clientList[i].write(client.name + " says " + message);    
      }  
    }
}
chatServer.listen(9000);

这里要说明一下的是,不不同操作系统对端口范围的限制不一样,有可能是随机的。

那么上面是不是一个完整功能的代码呢?我们说还有一个问题没有考虑进去:那就是一旦某个客户端退出,却仍保留在 clientList 里面,这明显是一个空指针(NullPoint)。如果是在这样的话我们写程序太脆弱了,能不能更健壮一些?——请接着看。

首先我们简单地把 client 从数组 clientList 中移除掉。完成这工作一点都不困难。Node TCP API 已经为我们提供了 end 事件,即客户端中止与服务端连接的时候发生。移除 client 对象的代码如下:

chatServer.on('connection', function(client) {
  client.name = client.remoteAddress + ':' + client.remotePort
  client.write('Hi ' + client.name + '!\n');

  clientList.push(client)

  client.on('data', function(data) {
    broadcast(data, client)
  })

  client.on('end', function() {
    clientList.splice(clientList.indexOf(client), 1); // 删除数组中的制定元素。这是 JS 基本功哦~
  })
})

但是我们还不敢说上述代码很健壮,因为一旦 end 没有被触发,异常仍然存在着。下面我们看看解决之道:重写 broadcast()

function broadcast(message, client) {
  var cleanup = []
  for(var i=0;i<clientList.length;i+=1) {
    if(client !== clientList[i]) {

      if(clientList[i].writable) { // 先检查 sockets 是否可写
        clientList[i].write(client.name + " says " + message)
      } else {
        cleanup.push(clientList[i]) // 如果不可写,收集起来销毁。销毁之前要 Socket.destroy() 用 API 的方法销毁。
        clientList[i].destroy()
      }

    }
  }  //Remove dead Nodes out of write loop to avoid trashing loop index 
  for(i=0;i<cleanup.length;i+=1) {
    clientList.splice(clientList.indexOf(cleanup[i]), 1)
  }
}

TCP API 中还提供一个 error 事件,用于捕捉客户端的异常:

client.on('error', function(e) {
  console.log(e);
});

Node 网络编程的 API 还丰富,此次仅仅是个入门,更多的内容请接着看,关于浏览器 Socket 应用。

Socket.IO

前面说到,浏览器虽然也属于客户端的一种,但仅支持“单工”的 HTTP 通讯。有见及此,HTML5 新规范中推出了基于浏览器的 WebSocket,开发了底层的接口,允许我们能进行更强大的操作,超越以往的 XHR。

如第一个例子那般,我们无须第三方框架就可以直接与 Node TCP 服务器 进行 Socket  通讯。

但我们又要认清一个事实,不是每个浏览器都可以顺利支持 WebSocket 的。于是 Socket.IO (Socket.IO)出现了,它提供了不支持 WebSocket 时候的降级支持,同时使得一些旧版本的浏览器也可以“全双工”地工作。优先使用的顺序如下:

  • WebSocket
  • Socket over Flash API
  • XHR Polling 长连接
  • XHR Multipart Streaming
  • Forever Iframe
  • JSONP Polling

经过封装,我们可以不探究客户端使用上述哪一种技术达致“全双工”;而我们编写代码时,亦无论考虑哪种放法,因为 Socket.IO 给我们的 API 只有一套。了解 Socket.IO 其用法就可以了。

先在浏览器部署 Socket.IO 的前端代码:

<!DOCTYPE html>
<html>
  <body>
    <script src="/socket.io/socket.io.js"></script> 
    <script> 
      var socket = io.connect('http://localhost:8080'); 
      // 当服务端发送一条消息到客户端,message 事件即被触发。我们把消息在控制台打印出来 
      socket.on('message', function(data){ console.log(data) }) 
    </script> 
  </body>
</html>

服务端 Node 代码:

var http = require('http'), 
io = require('socket.io'),
fs = require('fs');

// 虽然我们这里使用了同步的方法,那会阻塞 Node 的事件循环,但是这是合理的,因为 readFileSync() 在程序周期中只执行一次,而且更重要的是,同步方法能够避免异步方法所带来的“与 SocketIO 之间额外同步的问题”。当 HTML 文件读取完毕,而且服务器准备好之后,如此按照顺序去执行就能让客户端马上得到 HTML 内容。
var sockFile = fs.readFileSync('socket.html');

// Socket 服务器还是构建于 HTTP 服务器之上,因此先调用 http.createServer()
server = http.createServer();
server.on('request', function(req, res){
  // 一般 HTTP 输出的格式
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(sockFile);  
});

server.listen(8080);

var socket = io.listen(server); // 交由 Socket.io 接管

// Socket.io 真正的连接事件
socket.on('connection', function(client){
  console.log('Client connected');
  client.send('Welcome client ' + client.sessionId); // 向客户端发送文本
});

当客户端连接时,服务端会同时出发两个事件:server.onRequest 和 Socket.onConnection。它们之间有什么区别呢?区别在于 Socket 的是持久性的。

多个 Socket 连接,先是客户端代码:

<!DOCTYPE html>
<html>
  <body>
    <script src="/socket.io/socket.io.js"></script>
    <script>
      var upandrunning = io.connect('http://localhost:8080/upandrunning');
      var weather = io.connect('http://localhost:8080/weather');
      upandrunning.on('message', function(data){
        document.write('<br /><br />Node: Up and Running Update<br />');
        document.write(data);
      });
      weather.on('message', function(data){
        document.write('<br /><br />Weather Update<br />');
        document.write(data);
      });
    </script>
  </body>
</html>

服务端代码:

var sockFile = fs.readFileSync('socket.html');

server = http.createServer();
server.on('request', function(req, res){
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(sockFile);
});

server.listen(8080);

var socket = io.listen(server);

socket.of('/upandrunning')
  .on('connection', function(client){
    console.log('Client connected to Up and Running namespace.');
    client.send("Welcome to 'Up and Running'");
});

socket.of('/weather')
  .on('connection', function(client){
    console.log('Client connected to Weather namespace.');
    client.send("Welcome to 'Weather Updates'");
});
    

如上代码,我们可以划分多个命名空间,分别是 upandrunning 和 weather。

关于 Express 中使用 Soclet.io,可以参考《Node:Up and Ruuning》一书的 7.2.2 小节。

今晚时间的关系,涉及 Socket.io 许多方面还没有谈,容小弟我日后再了解。

  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
以下是一条 Node.js学习路线,供你参考: 1. 开始学习 JavaScript。掌握基本语法和特性,包括变量、数据类型、条件语句、循环语句、函数、对象、数组等。可以通过 W3School 或者 MDN 等网站进行学习。 2. 掌握 Node.js 的基本概念和基础知识。了解 Node.js 的历史、特点、应用场景等,同时学习 Node.js 的模块系统、事件循环、异步编程等基本概念。 3. 学习 Node.js 的核心模块。Node.js 提供了众多内置的模块,如文件系统、HTTP、网络、流等,需要掌握这些模块的使用方法。 4. 掌握 Node.js 的第三方模块。Node.js 生态系统非常丰富,有很多优秀的第三方模块,如 Express、Koa、Socket.io 等,需要学习这些模块的使用方法。 5. 学习数据库操作。Node.js 可以连接多种类型的数据库,如 MySQL、MongoDB、Redis 等,需要掌握数据库的基本操作和连接方法。 6. 学习 Web 开发。使用 Node.js 可以轻松创建 Web 应用程序,需要学习 HTML、CSS、JavaScript 等前端技术,以及 Express、Koa 等 Web 框架的使用方法。 7. 学习安全性。在开发 Web 应用程序时需要注意安全性,如 XSS、CSRF、SQL 注入等,需要掌握一些基本的安全知识和防御方法。 8. 学习性能优化。Node.js 应用程序需要考虑性能问题,如优化代码、减少请求响应时间、减少资源消耗等,需要学习一些性能优化技巧和工具。 9. 学习测试和调试。在开发 Node.js 应用程序时需要进行测试和调试,需要学习一些测试框架和工具,如 Mocha、Chai、SuperTest 等。 10. 学习部署和维护。Node.js 应用程序需要部署到生产环境中,并进行维护和监控,需要学习一些部署和维护技巧和工具,如 PM2、Nginx、Logrotate 等。 以上是 Node.js学习路线,需要不断实践和总结,才能掌握 Node.js 的核心技术和应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sp42a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值