关于Socket.IO的知识点记录

发布时间 2023-08-24 23:44:46作者: 漫思

关于Socket.IO的知识点记录

 

 


  最近因为项目的需要,开始学习nodejs,本着js的那点儿功底,nodejs学习起来还是挺快能上手的。随着深入学习,知道了express框架并那它写了一个小功能,作为一个php程序员哈,在express框架路由、模板渲染那里看到了Yii2的影子,所以便更加的亲切了。再接着便接触到了websocket,而今天谈论的socket.io 便是websocket的一个类库,说道这里了,我们先去了解下websocket和socket.io:

  

一  websocket

  WebSocket是html5新增加的一种通信协议,目前流行的浏览器都支持这个协议,例如 Chrome,Safrie,Firefox,Opera,IE等等,对该协议支持最早的应该是chrome,从chrome12就已经开始支持,随着协 议草案的不断变化,各个浏览器对协议的实现也在不停的更新。该协议还是草案,没有成为标准,不过成为标准应该只是时间问题了。

 

  1. WebSocket API

 

  首先看一段简单的javascript代码,该代码调用了WebSockets的API。

 

  var ws = new WebSocket(“ws://echo.websocket.org”);

 

  ws.onopen = function(){ws.send(“Test!”); };

 

  ws.onmessage = function(evt){console.log(evt.data);ws.close();};

 

  ws.onclose = function(evt){console.log(“WebSocketClosed!”);};

 

  ws.onerror = function(evt){console.log(“WebSocketError!”);};

 

  这份代码总共只有5行,现在简单概述一下这5行代码的意义。

 

  第一行代码是在申请一个WebSocket对象,参数是需要连接的服务器端的地址,同http协议使用http://开头一样,WebSocket协议的URL使用ws://开头,另外安全的WebSocket协议使用wss://开头。

 

  第二行到第五行为WebSocket对象注册消息的处理函数,WebSocket对象一共支持四个消息 onopen, onmessage, onclose和onerror,当Browser和WebSocketServer连接成功后,会触发onopen消息;如果连接失败,发送、接收数据 失败或者处理数据出现错误,browser会触发onerror消息;当Browser接收到WebSocketServer发送过来的数据时,就会触发 onmessage消息,参数evt中包含server传输过来的数据;当Browser接收到WebSocketServer端发送的关闭连接请求时, 就会触发onclose消息。我们可以看出所有的操作都是采用消息的方式触发的,这样就不会阻塞UI,使得UI有更快的响应时间,得到更好的用户体验。

  2 为什么引入WebSocket协议?

Browser已经支持http协议,为什么还要开发一种新的WebSocket协议呢?我们知道http协议是一种单向的网络协议,在建立连接后,它只 允许Browser/UA(UserAgent)向WebServer发出请求资源后,WebServer才能返回相应的数据。而WebServer不能 主动的推送数据给Browser/UA,当初这么设计http协议也是有原因的,假设WebServer能主动的推送数据给Browser/UA,那 Browser/UA就太容易受到攻击,一些广告商也会主动的把一些广告信息在不经意间强行的传输给客户端,这不能不说是一个灾难。那么单向的http协 议给现在的网站或Web应用程序开发带来了哪些问题呢?

让我们来看一个案例,现在假设我们想开发一个基于Web的应用程序去获取当前Web服务器的实时数据,例如股票的实时行情,火车票的剩余票数等等,这就需 要Browser/UA与WebServer端之间反复的进行http通信,Browser不断的发送Get请求,去获取当前的实时数据。下面介绍几种常 见的方式:

1.     Polling

这种方式就是通过Browser/UA定时的向Web服务器发送http的Get请求,服务器收到请求后,就把最新的数据发回给客户端(Browser /UA),Browser/UA得到数据后,就将其显示出来,然后再定期的重复这一过程。虽然这样可以满足需求,但是也仍然存在一些问题,例如在某段时间 内Web服务器端没有更新的数据,但是Browser/UA仍然需要定时的发送Get请求过来询问,那么Web服务器就把以前的老数据再传送过 来,Browser/UA把这些没有变化的数据再显示出来,这样显然既浪费了网络带宽,又浪费了CPU的利用率。如果说把Browser发送Get请求的 周期调大一些,就可以缓解这一问题,但是如果在Web服务器端的数据更新很快时,这样又不能保证Web应用程序获取数据的实时性。

2.     Long Polling

上面介绍了Polling遇到的问题,现在介绍一下LongPolling,它是对Polling的一种改进。

Browser/UA发送Get请求到Web服务器,这时Web服务器可以做两件事情,第一,如果服务器端有新的数据需要传送,就立即把数据发回给 Browser/UA,Browser/UA收到数据后,立即再发送Get请求给Web Server;第二,如果服务器端没有新的数据需要发送,这里与Polling方法不同的是,服务器不是立即发送回应给Browser/UA,而是把这个 请求保持住,等待有新的数据到来时,再来响应这个请求;当然了,如果服务器的数据长期没有更新,一段时间后,这个Get请求就会超 时,Browser/UA收到超时消息后,再立即发送一个新的Get请求给服务器。然后依次循环这个过程。

这种方式虽然在某种程度上减小了网络带宽和CPU利用率等问题,但是仍然存在缺陷,例如假设服务器端的数据更新速率较快,服务器在传送一个数据包给 Browser后必须等待Browser的下一个Get请求到来,才能传递第二个更新的数据包给Browser,那么这样的话,Browser显示实时数 据最快的时间为2×RTT(往返时间),另外在网络拥塞的情况下,这个应该是不能让用户接受的。另外,由于http数据包的头部数据量往往很大(通常有 400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。

通过上面的分析可知,要是在Browser能有一种新的网络协议,能支持客户端和服务器端的双向通信,而且协议的头部又不那么庞大就好了。WebSocket就是肩负这样一个使命登上舞台的。

3 websocket协议

 WebSocket协议是一种双向通信协议,它建立在TCP之上,同http一样通过TCP来传输数据,但是它和http最大的不同有两 点:1.WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和Browser/UA都能主动的向对方发送或接收数据,就像 Socket一样,不同的是WebSocket是一种建立在Web基础上的一种简单模拟Socket的协议;2.WebSocket需要通过握手连接,类 似于TCP它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。

下面是一个简单的建立握手的时序图:

这里简单说明一下WebSocket握手的过程。

当Web应用程序调用new WebSocket(url)接口时,Browser就开始了与地址为url的WebServer建立握手连接的过程。

1.     Browser与WebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。

2.     在TCP建立连接成功后,Browser/UA通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。

例如:

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==

Origin: http://example.com

Sec-WebSocket-Protocol: chat,superchat

Sec-WebSocket-Version: 13

3.     WebSocket服务器收到Browser/UA发送来的握手请求后,如果数据包数据和格式正确,客户端和服务器端的协议版本号匹配等等,就接受本次握手连接,并给出相应的数据回复,同样回复的数据包也是采用http协议传输。

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Protocol: chat

4.     Browser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表 示本次连接成功,触发onopen消息,此时Web开发者就可以在此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到 onerror消息,并且能知道连接失败的原因。

4 websocket与TCP,HTTP的关系

 WebSocket与http协议一样都是基于TCP的,所以他们都是可靠的协议,Web开发者调用的WebSocket的send函数在browser 的实现中最终都是通过TCP的系统接口进行传输的。WebSocket和Http协议一样都属于应用层的协议,那么他们之间有没有什么关系呢?答案是肯定 的,WebSocket在建立握手连接时,数据是通过http协议传输的,正如我们上一节所看到的“GET/chat HTTP/1.1”,这里面用到的只是http协议一些简单的字段。但是在建立连接之后,真正的数据传输阶段是不需要http协议参与的。

具体关系可以参考下图:

 

5 websocket server

     如果要搭建一个Web服务器,我们会有很多选择,市场上也有很多成熟的产品供我们应用,比如开源的Apache,安装后只需简单的配置(或者默认配置)就可 以工作了。但是如果想搭建一个WebSocket服务器就没有那么轻松了,因为WebSocket是一种新的通信协议,目前还是草案,没有成为标准,市场 上也没有成熟的WebSocket服务器或者Library实现WebSocket协议,我们就必须自己动手写代码去解析和组装WebSocket的数据 包。要这样完成一个WebSocket服务器,估计所有的人都想放弃,幸好的是市场上有几款比较好的开源库供我们使用,比如 PyWebSocket,WebSocket-Node, LibWebSockets等等,这些库文件已经实现了WebSocket数据包的封装和解析,我们可以调用这些接口,这在很大程度上减少了我们的工作 量。如

下面就简单介绍一下这些开源的库文件。

1.     PyWebSocket

PyWebSocket采用Python语言编写,可以很好的跨平台,扩展起来也比较简单,目前WebKit采用它搭建WebSocket服务器来做LayoutTest。

我们可以获取源码通过下面的命令

svn checkouthttp://pywebsocket.googlecode.com/svn/trunk/ pywebsocket-read-only

更多的详细信息可以从http://code.google.com/p/pywebsocket/获取。

2.     WebSocket-Node

WebSocket-Node采用JavaScript语言编写,这个库是建立在nodejs之上的,对于熟悉JavaScript的朋友可参考一下,另外Html5和Web应用程序受欢迎的程度越来越高,nodejs也正受到广泛的关注。

我们可以从下面的连接中获取源码

https://github.com/Worlize/Websocket-Node

3.     LibWebSockets

LibWebSockets采用C/C++语言编写,可定制化的力度更大,从TCP监听开始到封包的完成我们都可以参与编程。

我们可以从下面的命令获取源代码

git clone git://git.warmcat.com/libwebsockets

 值得一提的是:websocket是可以和http共用监听端口的,也就是它可以公用端口完成socket任务。

二 Socket.io

 

  node.js提供了高效的服务端运行环境,但是由于浏览器端对HTML5的支持不一,为了兼容所有浏览器,提供卓越的实时的用户体验,并且为程序员提供客户端与服务端一致的编程体验,于是socket.io诞生。Socket.io将Websocket和轮询 (Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。也就是说,Websocket仅仅是 Socket.io实现实时通信的一个子集。那么,Socket.io都实现了Polling中的那些通信机制呢?

 

  • Adobe® Flash® Socket
  • AJAX long polling
  • AJAX multipart streaming
  • Forever Iframe
  • JSONP Polling

 

  Adobe® Flash® Socket 大部分PC浏览器都支持的socket模式,不过是通过第三方嵌入到浏览器,不在W3C规范内,所以可能将逐步被淘汰,况且,大部分的手机浏览器都不支持这种模式。

 

AJAX long polling 这个很好理解,所有浏览器都支持这种方式,就是定时的向服务器发送请求,缺点是会给服务器带来压力并且出现信息更新不及时的现象。

 

AJAX multipart streaming  这是在XMLHttpRequest对象上使用某些浏览器(比如说Firefox)支持的multi-part标志。Ajax请求被发送给服务器端并保 持打开状态(挂起状态),每次需要向客户端发送信息,就寻找一个挂起的的http请求响应给客户端,并且所有的响应都会通过统一连接来写入

 

扯多了哈..........接着我就开始想写一个聊天室的demo出来,由于demo还在开发中,今天小编就不贴出来了,等写好了,我会在写一篇写的博客的,并会附上git clone 地址供大家参考学习交流的。

接下来是关于SOKCET.IO的知识记录:

安装Installing

$ npm install socket.io --save
  • 1

使用NODE.JS服务器Using with Node http server

Server (app.js)
var app = require('http').createServer(handler)
var io = require('socket.io')(app);
var fs = require('fs');

app.listen(80);

function handler (req, res) {
  fs.readFile(__dirname + '/index.html',
  function (err, data) {
    if (err) {
      res.writeHead(500);
      return res.end('Error loading index.html');
    }

    res.writeHead(200);
    res.end(data);
  });
}

io.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});
Client (index.html)
<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io('http://localhost');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>

使用EXPRESS.JS(3.-/4.-)框架Using with Express 3/4

服务器端(/app.js)Server (app.js)

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

server.listen(80);

app.get('/', function (req, res) {
  res.sendfile(__dirname + '/index.html');
});

io.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

客户端(/public/javascript/chat.js & /views/index.html)Client (index.html)

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>

使用更早的EXPRESS框架Using with the Express framework

Server (app.js)

var app = require('express').createServer();
var io = require('socket.io')(app);

app.listen(80);

app.get('/', function (req, res) {
  res.sendfile(__dirname + '/index.html');
});

io.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

客户端Client (index.html)

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>

收发定制事件Sending and receiving events

Socket.IO 提供了从服务器发布和接受定制事件的途径。不仅仅能够完成连接、发信、断开连接这样一般事件的收发,开发人员可以发布如下例所示的定制事件。
Socket.IO allows you to emit and receive custom events. Besides connect, message and disconnect, you can emit custom events:

服务端Server

//注意,io(<port>) 将会为您的应用创建一个http服务进程。
//note, io(<port>) will create a http server for you

var io = require('socket.io')(80);

io.on('connection', function (socket) {
  io.emit('this', { will: 'be received by everyone'});

  socket.on('private message', function (from, msg) {
    console.log('I received a private message by ', from, ' saying ', msg);
  });

  socket.on('disconnect', function () {
    io.emit('user disconnected');
  });
});

将事件限制在命名空间内Restricting yourself to a namespace

如果你希望在一个特定的web应用中控制多个消息与事件的收发,推荐使用切换方式(默认/其他命名空间)来实现。如果你希望调用第三方的代码,或者编写一些希望与他人共享的代码,socket.io提供了一种途径:规定socket的命名空间。这有助于创建多线连接:实现了不去创建两个websocket连接,而是使用一个连接完成多线操作。
If you have control over all the messages and events emitted for a particular application, using the default / namespace works. If you want to leverage 3rd-party code, or produce code to share with others, socket.io provides a way of namespacing a socket.
This has the benefit of multiplexing a single connection. Instead of socket.io using two WebSocket connections, it’ll use one.

服务端Server (app.js)

var io = require('socket.io')(80);
var chat = io
  .of('/chat')//!of(namespace)确定了这个连接所在的命名空间
  .on('connection', function (socket) {
    socket.emit('a message', {
        that: 'only'
      , '/chat': 'will get'
    });
    chat.emit('a message', {
        everyone: 'in'
      , '/chat': 'will get'
    });
  });

var news = io
  .of('/news')//!of(namespace)确定了这个连接所在的命名空间
  .on('connection', function (socket) {
    socket.emit('item', { news: 'item' });
  });

客户端Client (index.html)

<script>
  var chat = io.connect('http://localhost/chat')//!选择命名空间接受/发送特定的信息。
    , news = io.connect('http://localhost/news');//!选择命名空间接受/发送特定的信息。

  chat.on('connect', function () {
    chat.emit('hi!');
  });

  news.on('news', function () {
    news.emit('woot');
  });
  </script>

发送易变信息Sending volatile messages

有时候,某些消息的获取不是非常必要,甚至可以被抛弃。假想你有一个实时显示比伯这个关键词的所有推特的应用,如果某个客户端并没有准备好接受信息(因为网速慢或其他原因,又或者因为这些客户端正尝试通过长轮询的方式并且恰恰处在请求回应的过程之中)。如果这一次他们不能够完完全全地接收到比伯的实时推特不会影响这个应用的正常使用,在这种情况下,你就会希望将那些推特的信息当作易变信息(volatile messages)来发送。
Sometimes certain messages can be dropped. Let’s say you have an app that shows realtime tweets for the keyword bieber.
If a certain client is not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle), if they doesn’t receive ALL the tweets related to bieber your application won’t suffer.
In that case, you might want to send those messages as volatile messages.

服务端Server

var io = require('socket.io')(80);

io.on('connection', function (socket) {
  var tweets = setInterval(function () {
    getBieberTweet(function (tweet) {
      socket.volatile.emit('bieber tweet', tweet);//soket.volatile.emit就是这样的方式。
    });
  }, 100);

  socket.on('disconnect', function () {
    clearInterval(tweets);
  });
});

发送并获得返回数据Sending and getting data (acknowledgements)

有些时候,你可能希望得到客户端已经接收到信息的状态响应。
为了实现这样的期望,十分简单,只需要在.send或.emit函数的最后一个参量传递一个函数。不仅如此,当你使用.emit函数时,确认信息是你所指定的,意味着你还可以传递确认信息的内容。
Sometimes, you might want to get a callback when the client confirmed the message reception.
To do this, simply pass a function as the last parameter of .send or .emit. What’s more, when you use .emit, the acknowledgement is done by you, which means you can also pass data along:

服务端Server (app.js)

var io = require('socket.io')(80);

io.on('connection', function (socket) {
  socket.on('ferret', function (name, fn) {//传递一个fn函数
    fn('woot');//指定用于响应的确认信息
  });
});

客户端Client (index.html)

<script>
  var socket = io(); 
  // 小贴士:没有任何的参量的io()会进行自动检查。
  //TIP: io() with no args does auto-discovery
  socket.on('connect', function () { 
  //小贴士:你可以监听connect事件,而是直接监听ferret事件。
  // TIP: you can avoid listening on `connect` and listen on events directly too!
    socket.emit('ferret', 'tobi', function (data) {
      console.log(data); // data will be 'woot'
    });
  });
</script>

广播消息Broadcasting messages

想要广播一则消息,只需要添加一个broadcast标记给emit或send方法。广播意味着把消息发送给除了消息发送者以外的所有客户端。
To broadcast, simply add a broadcast flag to emit and send method calls. Broadcasting means sending a message to everyone else except for the socket that starts it.

服务端Server

var io = require('socket.io')(80);

io.on('connection', function (socket) {
  socket.broadcast.emit('user connected');
});

当作跨浏览器websocket使用Using it just as a cross-browser WebSocket

如果你只是希望使用websocket的语义,你也可以做到。只需要在消息的事件里调用send和listen方法。
If you just want the WebSocket semantics, you can do that too. Simply leverage send and listen on the messageevent:

服务端Server (app.js)

var io = require('socket.io')(80);

io.on('connection', function (socket) {
  socket.on('message', function () { });
  socket.on('disconnect', function () { });
});

客户端Client (index.html)

<script>
  var socket = io('http://localhost/');
  socket.on('connect', function () {
    socket.send('hi');

    socket.on('message', function (msg) {
      // my msg
    });
  });
</script>

########################################以上知识都是转载#########################################3

最后给大家推荐一篇socket.io简易教程(群聊,发送图片,分组,私聊)的文章,地址:http://blog.csdn.net/neuq_zxy/article/details/77531126