前面说到,浏览器虽然也属于客户端的一种,但仅支持“单工”的 HTTP 通讯。有见及此,HTML5 新规范中推出了基于浏览器的 WebSocket,开发了底层的接口,允许我们能进行 更强大的操作,超越以往的 XHR。
如第一个例子那般,我们无须第三方框架就可以直接与 Node TCP 服务器 进行 Socket 通讯。
但我们又要认清一个事实,不是每个浏览器都可以顺利支持 WebSocket 的。于是 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="https://www.jb51.net/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="https://www.jb51.net/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 小节。