学习WebSocket

一:为什么WebSocket

  在传统的互联网通信上,我们一般都是用http协议,客户端向服务器发起请求,服务器作出响应,返回静态资源或对数据库做读写操作等。但这种情况下,服务器是一个被动的角色,不能主动向客户端推送消息。想要达到“推送”的效果,一般只能采用轮询的方式,由客户端按一定时间间隔发起请求,如果数据有更新,则服务器返回新数据,客户端处理新数据并重新渲染。

      http虽然简单,但它基于“request--response”这种半双工方式,如果每次传递的数据很小,轮询的开销就显得大了。所以人们试图寻找一种更好的实现服务器端向客户端推送数据的方式。

      这时候HTML5定义了WebSocket协议。

      WebSocket是一个独立的创建在TCP上的协议,它使用ws或wss的统一资源标志符。使用和HTTP相同的端口号。ws---80,wss--443,通过HTTP/1.1的101状态码进行握手。

      它可以发送文本或二进制数据。没有同源限制。

创建websokect链接时,需要客户端先发起请求,然后服务器回应,这个过程称“握手”。

ws://example.com/wsapi
wss://secure.example.com/

二:WebSokect在前端的操作:

    我记得刚接触前端的时候,以为ajax是很复杂的东西。但接触后发现,前端的ajax不过是操作XMLHttpRequest对象。我们new了一个XMLHttpRequest对象后,调用它的open()、onreadystatechange()、setRequestHeader()、send()等方法,就能向指定的服务器发起GET/POST请求并设定回调,而其API底层已经帮我们封装了对http操作。

    WebSocket也是一样的,我们可以在js中通过new一个WebSocket对象,传入绝对URL,并调用其api来实现基于ws协议的双向连接。

 1 var ws = new WebSocket("wss://echo.websocket.org");
 2 //创建连接成功,可以准备通讯
 3 ws.onopen = function(evt) { 
 4   console.log("Connection open ..."); 
 5   ws.send("Hello WebSockets!");
 6 };
 7 //接收数据
 8 ws.onmessage = function(evt) {
 9   console.log( "Received Message: " + evt.data);
10   ws.close();
11 };
12 //即将关闭连接
13 ws.onclose = function(evt) {
14   console.log("Connection closed.");
15 };   

      我们使用ajax时会监听其readyState,当readyState值为4时表示响应已接收并调用回调。

      在WebSocket里也有一个常量readyState用来描述 WebSocket 连接的状态。

学习WebSocket

对应于这几个状态的监听方法有:

WebSocket.onclose()  

WebSocket.onerror()  

WebSocket.onmessage()

WebSocket.onopen()

      在Writing WebSocket client applications这篇websocket实践的文章中提到使用websocket时要注意的一些地方:

      *当连接发生错误时,一个命名为“error”的事件会发送到WebSocket实例对象上并触发onerror监听函数。随后CloseEvent事件会发送到WebSocket实例对象上并触发onclose回调函数。

      *由于发起连接是异步的,所以WebSocket实例对象的send()方法应该放在onopen()的回调中。

      *推荐使用json格式传输数据,考虑到在某些版本的火狐浏览器中,websocket只支持发送字符串。

      *WebSocket的api都是事件驱动的,所以我们要习惯用回调的方式处理数据的接收:exampleSocket.onmessage = function (event){//do sth...}

 1 // Send text to all users through the server
 2 function sendText() {
 3   // Construct a msg object containing the data the server needs to process the message from the chat client.
 4   var msg = {
 5     type: "message",
 6     text: document.getElementById("text").value,
 7     id:   clientID,
 8     date: Date.now()
 9   };
10 
11   // Send the msg object as a JSON-formatted string.
12   exampleSocket.send(JSON.stringify(msg));
13   
14   // Blank the text input element, ready to receive the next line of text from the user.
15   document.getElementById("text").value = "";
16 }
View Code

三:服务器端的WebSocket

现今,常见的服务器几乎都支持websocket,详见,我自己学的是node,就研究下node中如何实现websocket。

阮一峰老实在其博客中给出了常用的 Node 实现:

那我就试试Socket.IO吧。

----------------------------------------------------------------------------实验开始----

假设我们已经安装了node并且部署了一个简单的http服务器,返回一个简单的聊天界面。

学习WebSocket

Socket.IO的官方文档提到其分为两部分:

    服务器端的整合到NodeJS HTTP服务器的部分:socket.io

    加载到浏览器端的前端库:socket.io-client

 在开发阶段,socket.io能自动帮我们服务客户端,所以这时候只需要安装socket.io即可。

npm install --save socket.io

服务器端:在app.js中修改js:

var app = require('express')();
var http = require('http').Server(app);
//将http传进去,创建一个io实例
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});
//监听connection事件
io.on('connection', function(socket){
  console.log('a user connected');
});

http.listen(3000, function(){
  console.log('listening on *:3000');
});

客户端:在body标签结束前添加下面几行代码:

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

这几行代码用来加载客户端的  socket.io-client,暴露一个全局的io,并默认和 该页面的服务器创建连接。

      这里要留意到,文档中服务器端io监听connection时,回调中的参数对象是socket

      在客户端页面上创建的对象名也是socket

这表示,服务器端每监听到一次connection事件,其回调socket都对应一个客户端上的socket.

服务器端socket可以触发一个disconnect事件,结束连接:

1 io.on('connection', function(socket){
2   console.log('a user connected');
3   socket.on('disconnect', function(){
4     console.log('user disconnected');
5   });
6 });

------------------------------------------------------------------------------

以上是对服务器端脚本和客户端脚本的socket.io配置,接下来,我们要怎么实现一个聊天室功能呢?(也就是一个客户端发送消息到服务器,再由服务器推发到所有客户端)

事件触发

socket.io库背后的理念是,让我们可以通过接收或发送 事件 来传送数据。事件类型是自定义的。

客户端:

 1 <script>
 2   $(function () {
 3     var socket = io();
 4     $('form').submit(function(){
 5       socket.emit('chat message', $('#m').val());
 6       $('#m').val('');
 7       return false;
 8     });
 9   });
10 </script>

服务器脚本:

1 io.on('connection', function(socket){
2   socket.on('chat message', function(msg){
3     console.log('message: ' + msg);
4   });
5 });

可见,这种方式其实就是一般的“订阅/发布”模式。不过此时服务器是观察者罢了。

通过这种方式,我们可以让服务器接收一个客户端的消息,可是,怎么推送到所有客户端呢?

 Socket.IO提供了io.emit方法:

io.emit('some event', { for: 'everyone' });

还有另一个方法,让我们可以广播(给该socket以外的其他socket):

1 io.on('connection', function(socket){
2   socket.broadcast.emit('hi');
3 });

下面是官网给出的一个在服务器端向所有socket发送消息的示例代码:

1 io.on('connection', function(socket){
2   socket.on('chat message', function(msg){
3     io.emit('chat message', msg);
4   });
5 });

客户端收发消息:

 1 <script>
 2   $(function () {
 3     var socket = io();
 4     $('form').submit(function(){
 5       socket.emit('chat message', $('#m').val());
 6       $('#m').val('');
 7       return false;
 8     });
 9     socket.on('chat message', function(msg){
10       $('#messages').append($('<li>').text(msg));
11     });
12   });
13 </script>

    参考:https://zh.wikipedia.org/wiki/WebSocket

               WebSocket 教程

              Get Started: Chat application