精华内容
下载资源
问答
  • web实时通信技术
    千次阅读
    2015-03-20 18:16:11

    随着web技术的发展和硬件的革新,web应用已逐渐在侵占着C/S软件的领域。不得不说,B/S模式的应用将是未来的主流,当然这只是本人的一家之言.当用户在浏览器中能做的事情越来越多的时候,当用户慢慢习惯只用浏览器来获得一切需求的时候,到那时他们会发现原来他们的电脑只需要一个浏览器,仅此而已。这就是ChromeBook的发展理念吧。当然这个时候什么时候能到来却是个未知数,扯远了。

    今天要说说的是关于在浏览器中即时通讯的问题.到现在你会发现web即时通讯应用到处都能见到。很多网站都有即时通讯的应用。首先介绍几个比较知名的web IM应用:webqq,网页fetion,开心网,白社会等等。

    一个web即时通讯应用主要要克服两个问题:

    • 信息的持久化
    • 信息的传递。

    第一个问题很简单,选择也挺多,比如你可以选择数据库,文件,临时的缓存等来保持聊天信息。具体依据你的项目需求来做决定。本文主要是要说说这第二个问题——信息的传递。

    web通信方式对服务器来说也主要有两种方式:推和拉。

    所谓“推”即服务器收到了数据传递请求时主动向对方客户端推送的方式。这也是一种理想的方式,但却是一直很难实现的技术难题。

    “拉”即是客户端采用轮询的方式来不断对服务器进行http请求,这是现在广泛使用的方式。

    web即时通讯的信息传递一直以来有个问题那就是:如何保证当有信息需要传递的时候即时的传递给对方?也许你会觉得这不是个问题,当时当你知道了http是个无状态无连接的协议时,你会发现这是个很大的问题。让我们先看看针对这个问题才采用的解决方案的发展历程。

    阶段一:指定页面全部或局部的刷新时间间隔

    最初为了更新客户端的数据信息采用的方式是通过<meta>内指定自动刷新的时间间隔,这样浏览器会在该时间到时自动刷新该页面。这是最初的解决更新客户端信息的方式,也是最原始也是最蹩脚的方式。现在一些手机网页上还有采用这种方式,我记得以前用手机看nba文字直播的时候就是这种方式。这种用户体验让人发指,缺点显而易见。当然还有一种实现原理一样的局部刷新方式,就是内嵌隐藏的iframe来不断刷新。早期很多就是采用的这种方式。

    阶段二:Ajax的轮询

    Ajax技术的诞生,给我们带来了很大的惊喜。就是由于ajax技术使得我们可以采用异步的方式对服务器发起请求,这对用户体验绝对是一个相当大的改善。ajax轮询就是结合js的setInterval函数指定时间间隔发起http请求。但是这种方式的缺点也是很明显的,这个时间间隔你觉得取多少合适呢?

    阶段三:Ajax长轮询 —— Comet

    Comet技术的出现还是受到了很大的关注。比较这一问题至始至终都没有得到一个好的解决方案。首先先介绍介绍这个Comet。Comet 是一种新的 Web 应用架构。基于这种架构开发的应用中,服务器端会主动以异步的方式向客户端程序推送数据,而不需要客户端显式的发出请求。Comet 架构非常适合事件驱动的 Web 应用,以及对交互性和实时性要求很强的应用,如股票交易行情分析、聊天室和 Web 版在线游戏等。它的主要不同之处就在于它会客户端保持一种长连接状态,直到消息被传递后才释放。其实到这里这才是真正的算得上实时通讯了。我去网上查了下这个技术一般都是和Java使用。Tomcat6已支持了Comet技术。然后我本想找找PHP和Comet结合的例子,得到的尽是无情的鄙视。Comet实现长连接有两种方式:

    • HTTP 流(HTTP Streaming)这种情况下,客户端打开一个单一的与服务器端的 HTTP 持久连接。服务器通过此连接把数据发送过来,客户端增量的处理它们。
    • HTTP 长轮询(HTTP Long Polling这种情况下,由客户端向服务器端发出请求并打开一个连接。这个连接只有在收到服务器端的数据之后才会关闭。服务器端发送完数据之后,就立即关闭连接。客户端则马上再打开一个新的连接,等待下一次的据。

    这种方式实现通讯方式相比已经有很大提高,至少能对得起实时通信这个称号。现在很多实时通讯应用就是基于此技术。如webqq。当然它的缺点就是要维持这个长连接明显加大了服务器的负担。

    接下来或许是HTML5 WebSocket…

    WebSocket 是HTML5一种新的协议。它是实现了浏览器与服务器的双向通讯。在 WebSocket API 中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。这种方式不仅简单,而且降低了服务器的要求。只是至少要等到你的用户使用的浏览器支持它了再说。

    其他插件式的通信方式

    其实在上述发展的过程中,还有一种通讯方式存在,那就是使用activeX控件或者JAVA Applet等实现Socket通信。还有使用的较为广泛的flash。其实这种通信方式是最划算的。但是不足的是原生浏览器并不支持,需要用户安装相关的浏览器插件才行。于是我便可以不多说了。谁爱装插件谁装去(flash除外,没办法)。

    好了,这里只是简单的说下web通讯方面的一些技术,如果你要想研究透其中的某一项都得花些时间专研才行。

    更多相关内容
  • Web实时通信技术

    千次阅读 2016-08-19 21:11:41
    本周在应用宝前端分享会上分享了Web实时通信技术,分享内容整理如下。一、传统Web数据更新传统的Web数据更新,必须要刷新网页才能显示更新的内容。这是浏览器采用的是B/S架构,而B/S架构是基于HTTP协议的。HTTP协议...

    本周在应用宝前端分享会上分享了Web实时通信技术,分享内容整理如下。

    一、传统Web数据更新

    传统的Web数据更新,必须要刷新网页才能显示更新的内容。这是浏览器采用的是B/S架构,而B/S架构是基于HTTP协议的。HTTP协议的工作模式就是客户端向服务器发送一个请求,服务器收到请求后返回响应。所以这种工作模式是基于请求显示数据的。

    这样的工作方式有其自身的好处,但是也会导致很多问题。在Web应用越来越火的今天,经常会遇到需要服务器主动发送数据到客户端的需求,比如事件推送、Web聊天等。这些需求使用传统的Web数据更新工作模式是无法实现的,因此就需要一项新的技术:Web实时通信技术。

    二、短轮询

    第一种解决方法思路很简单,既然需要客户端发送请求服务器才能发送数据,那么就可以让客户端不断的向服务器发送数据,这样就能实时的获取服务器端的数据更新了。具体的实现方法很简单,客户端每隔一定时间就发送一个请求到服务器端。下面的图可以清晰的反映出短轮询过程中客户端和服务器的工作流程:

    下面看一下实现方法。

    在服务器端我们模拟数据的发送,生成1-1000的随机数,当数值小于800的时候模拟没有数据的情况,大于800的时候模拟有数据的情况,并返回数据:

    <?php
    $arr = array('title'=>'推送!','text'=>'推送消息内容');
    $rand = rand(1,999);
    if(rand < 800){
    echo “”
    }else{
      echo json_encode($arr);
    }
    ?>

    客户端部分,定义了一个函数用来发送ajax请求到客户端,然后每隔2s就发送以此请求:

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>短轮询ajax实现</title>
    <script type="text/javascript" src="../jquery.min.js"></script>
    </head>
    <body>
    <form id="form1" runat="server">
         <div id="news"></div>
        </form>
    </body>
    <script type="text/javascript">
      function showUnreadNews()
        {
            $(document).ready(function() {
                $.ajax({
                    type: "GET",
                    url: "setInterval.php",
                    dataType: "json",
                    success: function(msg) {
                        $.each(msg, function(id, title) {
                            $("#news").append("<a>" + title + "</a><br>");
                        });
                    }
                });
            });
        }
        setInterval('showUnreadNews()',2000);
    </script>
    </html>

    运行程序我们可以在Chrome的network工具看到,每隔两秒都会有一个请求从客户端发往服务器,不管当时的服务器有没有数据,都会立即返回请求。、

    短轮询虽然简单,但是它的缺点也是显而易见的。首先短轮询建立了很多HTTP请求,而且其中绝大部分的请求是没有用处的。而HTTP连接数过多过多会严重影响Web性能。其次,客户端设置的请求发送时间间隔也不好掌控,时间间隔太短会造成大量HTTP的浪费,而时间间隔过长会使得客户端不能即时收到服务器端的数据更新,失去了即时通信的意义。

    三、长轮询

    针对上面短轮询的种种问题,我们自然而然想到要减少HTTP请求的数量,才能让实时通信性能更高。而长轮询就能有效的减少HTTP请求的数量。

    长轮询的逻辑是,首先客户端向服务器端发送一个请求,服务器端在收到请求后不马上返回该请求,而是将请求挂起。一段时间后服务器端有数据更新时,再将这个请求返回客户端,客户端收到服务器端的响应数据后渲染界面,同时马上再发送一个请求到服务器,如此循环,下面的图描述了这个过程:

    长轮询有效的减少了HTTP连接。服务器端在有数据更新时才返回数据,客户端收到数据再请求这一机制,较少了之间许多的无用HTTP请求。下面通过一个Demo来演示长轮询的工作模式。

    服务器端模拟数据更新,在客户端发来请求后先挂起6s,模拟6s后才有数据的情况:

    <?php
    $arr = array('title'=>'推送','text'=>'推送消息内容');
    $flag = 0;
    for($i=1;$i<=6;$i++){
      if($i>5){   //i = 6时表示有数据了
        echo json_encode($arr);
      }else{
        sleep(1);
      }
    }
    ?>

    这里为了演示的更清楚,添加了一个for循环,其实就是先将请求挂起6s。

    客户端发送一个ajax请求,并当收到服务器端数据后自动再发送一个请求到服务器:

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>长轮询ajax实现</title>
    <script type="text/javascript" src="../jquery.min.js"></script>
    </head>
    <body>
    <input type="button" id="btn" value="click">
    <div id="msg"></div>
    </body>
    <script type="text/javascript">
    $(function(){
            $("#btn").bind('click',{btn:$('#btn')},function(e){
                $.ajax({
                    type: 'POST',
                    dataType: 'json',               
                    url: 'do.php',
                    timeout: '20000',
                    success: function(data,status){
                        $("#msg").append(data.title + ':' + data.text + "</br>");
                        e.data.btn.click(); 
                    }
                });
            });
        });
    </script>
    </html>

    长轮询虽然有效的减少了HTTP请求,但是HTTP请求相比之下还是很多的,因为每次数据的更新都需要建立一个HTTP请求。下面的技术就可以实现建立以此HTTP连接,服务器可以源源不断的向客户端发送数据。

    四、SSE

    MessageEvent是HTML5中新定义的一种事件基类,和原先的MouseEvent、UIEvent一样。MessageEvent是专门为数据传输定义的事件,HTML5中的SSE和WebSocket都利用了这个事件。MessageEvent在HTML5协议中的接口如下:

    除了继承了Event事件具有的属性外,MessageEvent还定义了其他属性。其中data属性所包含的内容就是传输的数据内容。而lastEventId可以存放一个事件标识符,当客户端和服务器传输数据过程中断开连接需要重连时,客户端会将上一次传输数据的lastEventId作为请求头中的一个特殊字段发送到服务器,从而让服务器可以继续上次断开连接的部分发送消息。

    SSE是HTML5规范中定义的,它可以实现维持一个HTTP连接,服务器端单向向客户端不断发送数据。这个技术真正意义上实现了服务器主动推送数据到客户端。除此之外,SSE的客户端和服务器端代码都特别简洁,使用起来十分方便。

    SSE的逻辑就是首先客户端发送请求,建立一个HTTP连接,之后服务器端和客户端一直保持这个连接,服务器端可以单向向客户端发送数据,见下图:

    SSE的实现方法很简单,SSE是以事件流的形式发送数据的。在服务器端要先进行如下配置:

    Content-Type:text/event-stream
    Cache-Control:no-cache
    Connections:keep-alive

    如果还需要进行跨域,配置里再添加:

    Access-Control-Allow-Origin: *

    其中text/event-stream是HTML5规范中为SSE的事件流传输定义的一种流格式。

    做好配置后,服务器第二个要做的事就是维护一个清单,清单内容就是要向客户端发送的数据,下面是一段例子:

    data: first event  
     
    event: push
    data: second event

    每一组事件流传输对应的数据,每个事件流之间使用换行符进行分割。这种格式发送过去之后会被进行解析,最后将各个部分进行组装,客户端按需进行读取。每一个事件流可以为其指定四个字段。

    (1)retry字段

      SSE有一个自动重连机制,即客户端和服务器之间的连接断开后,隔一段时间客户端就会自动重连。retry指定的就是这个重连时间,单位为ms。

    (2)event字段

    SSE中有三个默认的事件,它们都继承自MessageEvent事件类。其中open事件在连接建立时触发,message事件在从客户端接收到数据时触发,close事件在连接关闭时触发。如果传输事件时一个事件流没有设定event字段的值,那么客户端就会监听message默认事件;如果指定了event事件,那么就会生成自定义的event事件,客户端可以监听自定义的event进行数据读取。

    (3)data字段

    data字段包含的内容就是服务器要传送给客户端的数据,SSE只能传送文本数据,且必须是UTF-8编码的。由于这些事件都继承自MessageEvent基类,因此可以通过event.data获取服务器传输的数据。

    (4)id字段

    id字段是事件的唯一标识符,解析后会被传入MessageEvent对应的lastEventId属性字段中,从而记录上次数据传输的位置。如果不指定id字段lastEventId字段就是一个空字符串。

    至此服务器端任务完成,下面介绍客户端的实现方法。

    客户端首先需要实例化一个EventSource对象。EventSource对象在HTML5中的接口定义如下:

    首先需要为EventSource对象传入一个url,表明要请求的服务器地址。该对象有三个readyState状态值,其中CONNECTING表示正在建立连接,OPEN表示连接处于打开状态可以传输数据,CLOSED状态表示连接中断,并且客户端并没有尝试重连。EventSource定义的默认事件句柄为onopen、onmessage、onerror。其中的方法只有close(),用来关闭连接。

    实例化好EventSource对象后,我们需要对事件进行监听,从而获取数据,最后可以通过close()方法关闭连接,整体逻辑的代码如下:

    var es = new EventSource(url);  
    es.addEventListener("message", function(e){
        console.log(e.data);
    })
    es.close();

    下面是一个实现SSE的例子。

    服务器使用node,代码及注释如下:

    var http = require("http");
    var fs = require("fs");
    //创建服务器
    http.createServer(function (req, res) {
      var index = "./index.html";
      var fileName;
      var interval;
      var i = 1;
      //设置路由
      if (req.url === "/"){
        fileName = index;
      }else{
        fileName = "." + req.url;
      }
      if (fileName === "./stream") {
        //配置头部信息:注意类型为专门为sse定义的event-stream,并且不使用缓存
        res.writeHead(200, {"Content-Type":"text/event-stream", "Cache-Control":"no-cache", "Connection":"keep-alive"});
        /*
          下面的代码的输出结果等价于:
          retry: 10000
          event: title
          data: News Begin
     
          data: ...
     
          ...
        */
        //上面可以看出,只有第一段是触发事件connecttime,其他都是触发默认事件message
        res.write("retry: 10000\n");    //定义连接断开后客户端重新发起连接的时间,ms制
        res.write("event: title\n");   //自定义的事件title
        res.write("data: News Begin! \n\n");
        //每隔1s就在协议中新写入一段数据来模拟服务器向客户端发送数据
        interval = setInterval(function() {
          res.write("data: News" + i +"\n\n");
          i++;
        }, 1000);
        //监听close事件,当服务器关闭时停止向客户端传送数据
        req.connection.addListener("close", function () {
          clearInterval(interval);
        }, false);
      } else if (fileName === index) {
        fs.exists(fileName, function(exists) {
          if (exists) {
            fs.readFile(fileName, function(error, content) {
              if (error) {
                res.writeHead(500);
                res.end();
              } else {
                res.writeHead(200, {"Content-Type":"text/html"});
                res.end(content, "utf-8");
              }
            });
          } else {
            res.writeHead(404);
            res.end();
          }
        });
      } else {
        res.writeHead(404);
        res.end();
      }
    }).listen(8888);
    console.log("Server running at http://127.0.0.1:8888/");

    服务器端自定义了title事件,用来发送标题数据,其他的数据使用默认事件发送。

    客户端部分代码及注释如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <title>Server-Sent Events Demo</title>
      <meta charset="UTF-8" />
      <script>
        window.onload = function() {
          var button = document.getElementById("connect");
          var status = document.getElementById("status");
          var output = document.getElementById("output");
          var connectTime = document.getElementById("connecttime");
          var source;
          function connect() {
            source = new EventSource("stream");
            //messsage事件:当收到服务器传来的数据时触发
            source.addEventListener("message", function(event) {
              output.textContent = event.data;  //每次收到数据后都更新时间
            }, false);
            //自定义的事件title
            source.addEventListener("title", function(event) {
              connectTime.textContent = event.data;
            }, false);
            //open事件:当客户端和服务器完成连接时触发
            source.addEventListener("open", function(event) {
              //每次连接成功后更新按钮功能和文本提示,再次点击按钮应为关闭连接
              button.value = "Disconnect";
              button.onclick = function(event) {
                //调用eventsource对象的close()方法关闭连接,并且为其绑定新的事件connect建立连接
                source.close();
                button.value = "Connect";
                button.onclick = connect;
              };
            }, false);
            //异常处理
          }
          //调用,如果支持EVentSource则执行connect()方法仅从sse连接
          connect();
        }
      </script>
    </head>
    <body>
      <input type="button" id="connect" value="Connect" /><br />
      <span id="status"></span><br />
      <span id="connecttime"></span><br />
      <span id="output"></span>
    </body>
    </html>

    客户端监听事件,不同的事件收到的数据进行不同的渲染。同时,都过为按钮绑定事件,调用close()等方法,实现SSE连接的打开与断开。

    SSE技术简单方便,且是HTML5中定义内容,实现了服务器推送数据的技术,下图是SSE的浏览器兼容性列表:

    但是SSE只能实现服务器到客户端单向的数据传输。有时我们的需求需要使用双向数据传输,这时就需要使用WebSocket。

    五、WebSocket

    WebSocket也是HTML5中定义的。它是一个新的协议,实现了全双工的通信模式,即客户端和服务器端可以互相发送消息。WebSocket的实现首先需要客户端和服务器端进行一次握手,此后就会建立起一个数据传输通道,通道存在期间客户端和服务器端可以平等的互相发送数据。具体的逻辑图如下:

    WebSocket的服务器端实现比较复杂,但是各个后台语言都已经有实现好的WebSocket库。比如Node.js中的nodejs-websocket模块和socket.io模块。使用WebSocket技术可以实现很多功能,附件中就是借助nodejs-websocket模块编写的弹幕效果。

    WebSocket的客户端实现比较便捷,首先需要实例化一个WebSocket对象,传入要请求的服务器的url。这里需要注意,协议名要指定为ws或wss,如:

    ws = new WebSocket("ws://localhost:8080");

    客户端可以通过调用send()方法进行数据的发送,通过调用close()方法关闭WebSocket连接。WebSocket也使用了MessageEvent接口,因此可以对消息事件进行监听,默认的可以通过监听message事件获取数据。下面是WebSocket在HTML5规范中定义的接口:

    WebSocket的服务器端实现可以分为两个部分,第一个部分是握手部分,主要负责HTTP协议的升级,第二个部分是数据传输部分。

    WebSocket协议可以说是一个HTTP协议的升级版,这个升级过程需要通过客户端和服务器的一次握手来实现。下面是建立握手时客户端向服务器发送的请求报文头实例:

    字段Upgrade:websocket和Connection:Upgrade部分完成了协议的升级,服务器可以触发响应事件,获取这两个字段的内容,匹配符合要求后服务器端进行握手处理。客户端需要向服务器端发送一个Sec-WebSocket-Key字段,这个字段的内容是客户端产生的,相当于一个私钥。服务器端收到客户端的请求头后,如果确定是要使用WebSocket协议,就开始进行握手。服务器端使用客户端传来的Sec-WebSocket-Key的值,与服务器端存储的一个全局唯一标识符进行拼接,之后做SHA1处理和BASE64加密,并作为响应头返回给客户端。服务器端的GUID相当于公钥。

    下面是服务器端返回的响应报文头,处理后的字符串在Sec-WebSocket-Accept字段中给出。客户端必须受到101状态码,这个状态码表示切换协议,从而完成对协议的升级。

    总的来看,WebSocket只有在建立握手连接的时候借用了HTTP协议的头,连接成功后的通信部分都是基于TCP的连接,可以说WebSocket协议是HTTP协议的升级版。

    WebSocket的数据帧格式如下:

    opcode存储的是传输数据的类型,诸如文本、二进制数据等。数据传输时首先会对该部分的值进行判断,然后进行对应的数据操作。数据存储在Payload Data字段中。最后将帧结构解析为一个键值对的对象。

    下面是浏览器对WebSocket的支持情况:

    展开全文
  • 介绍了RTC Web目前的研究现状,对有关RTC Web关键技术标准进行了分析,介绍了谷歌的Web RTC技术开发路线图在RTC Web架构方面,重点分析了Skype提出的媒体部件模型架构与思科提出的AdProp模式和offAns模式。...
  • 随着互联网技术的不断发展,Web技术在各个领域得到了不同程度的运用,人们对于Web应用的实时性提出了更高的要求,HTML5 WebSocket协议因此得到了广泛的关注。通过对基于HTTP的传统Web实时通信方案进行分析,针对其中...
  • Web实时通信服务器Push机制的研究,冯恺,双锴,在互联网Web技术高速发展的今天,人们对Web浏览器实时通信领域的服务器Push机制越发关注。本文在介绍基于HTTP协议的服务器Push机制的基
  • 摘要:本文在比较传统的实时web通讯技术与研究HTML5 WebSocket技术基础上,通过研究WebSocket技术实时WEB通讯中的应用,体现出WebSocket在B/S模式中的应用优势与价值。  1.前言  作为下一代的Web标准,HTML5...
  • Web即时通信技术 -- Web Socket

    千次阅读 2021-12-01 11:47:36
    WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为RFC 6455,后由RFC 7936补充规范。 WebSocket 使得客户端和服务器之间的数据...

    WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455,后由 RFC 7936 补充规范。

    WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

    优点

    • 在客户端和服务器之间保有一个持有的连接,两边可以随时给对方发送数据,有很强的实时性;

    • 属于应用层协议,基于TCP传输协议,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器;

    • 可以发送文本,也可以支持二进制数据的传输;

    • 数据格式比较轻量,性能开销小,通信高效;

    • 没有同源限制,客户端可以与任意服务器通信;

    • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL;

     

     客户端实现

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
      </head>
      <body>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %></p>
        <div id="clock"></div>  
      </body>  
      <script>    
        let socket = new WebSocket('ws://localhost:8888')
        socket.onopen = function () {      
          console.log('1. 客户端连接上了服务器', new Date().getTime());      
          socket.send('3. 你好')    
        }    
        socket.onmessage = function (e) {      
          console.log('6',e.data);    
        }
      </script>
    </html>
    

     服务端实现

    var createError = require('http-errors');
    var express = require('express');
    var path = require('path');
    var cookieParser = require('cookie-parser');
    var logger = require('morgan');
    
    var indexRouter = require('./routes/index');
    var usersRouter = require('./routes/users');
    
    var app = express();
    
    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', indexRouter);
    app.use('/users', usersRouter);
    
    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
      next(createError(404));
    });
    
    // error handler
    app.use(function(err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};
    
      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });
    
    let WebSocket = require('ws')
    let wss = new WebSocket.Server({port:8888})
    wss.on('connection', function(ws) {  
        console.log('2.服务器监听到了客户端的连接', new Date().getTime());  
        ws.on('message',function(data){    
            console.log('4.客户端发来的消息',data);    
            ws.send('5.服务端说:你也好')  
        })
    })
    
    module.exports = app;
    

    展开全文
  • WebSocket协议实时通信技术原理

    千次阅读 2017-11-16 11:24:04
    WebSocket通信协议是HTML5支持浏览器与服务器进行多路复用...客户端发送的Request请求信息当中不再带有请求头Head的部分信息,与Ajax长轮询通信对比,WebSocket通讯,不仅能降低服务器的压力而且保证了数据的实时传输。

    HTML5 WebSocket协议实时通讯机制

    Ajax长轮询过程中,客户端通过频繁地向服务器发送HTTP请求的方式与服务器保持这一种虚拟的连接,此连接的方式属于循环连接而不属于长连接。

    相对于HTTP协议这一非持久连接的特点来说,为避免HTTP轮询的滥用,2011年,由IETF(互联网工程任务组)制定并规范了WebSocket通信协议。

    WebSocket通信协议是HTML5支持浏览器与服务器进行多路复用全双工(Full-Duplex)通信的技术,是一个持久化协议,允许服务器主动发送信息给客户端。客户端通过JavaScript实现相应的API与服务器建立WebSocket连接后,客户端发送的Request请求信息当中不再带有请求头Head的部分信息,与Ajax长轮询通信对比,WebSocket通讯,不仅能降低服务器的压力而且保证了数据的实时传输。

    ####WebSocket协议实时通信技术原理

    最喜欢拆东西,解剖技术原理了。做事多问个为什么,凭什么要这样实现,这样做的作用是什么。

    WebSocket协议是基于TCP协议并遵从HTTP协议的握手规范的一种通讯协议,其通过发送连接请求,握手,验证握手信息这三个步骤与服务器建立WebSocket连接。

    这里写图片描述


    • 发送连接请求

    客户端通过一个格式为:ws://host:port/的请求地址发起WebSocket连接请求,并由JavaScript实现WebSocket API与服务器建立WebSocket连接,其中host为服务器主机IP地址或域名,port为端口。为了让本客服系统能够在不同浏览器建立WebSocket连接,在发送连接请求前,需要开启SockJS的支持,创建一个全双工的信道。

    WebSocket请求头信息:

    这里写图片描述

    相关字段说明:

    字段名说明
    Connection:Upgrade标识该HTTP请求是一个协议升级请求
    Upgrade: WebSocket协议升级为WebSocket协议
    Sec-WebSocket-Version: 13客户端支持WebSocket的版本
    Sec-WebSocket-Key:jONIMu4nFOf0iwNnc2cihg==客户端采用base64编码的24位随机字符序列。
    Sec-WebSocket-Extensions协议扩展类型

    HTTP协议和WebSocket协议关系图:

    这里写图片描述

    可以看出WebSocket请求是HTTP协议进行升级的,即使请求格式为ws://,其本质也是一个HTTP请求,借用了HTTP的部分设施兼容了客户端的握手规则。


    • 握手

    当服务器收到请求后,会解析请求头信息,根据升级后的协议判断该请求为WebSocket请求,并取出请求信息中的Sec-WebSocket-Key字段的数据按照某种算法重新生成一个新的字符串序列放入响应头Sec-WebSocket-Accept中。

    WebSocket服务器响应头信息:

    这里写图片描述

    相关字段说明:
    Sec-WebSocket-Accept:服务器接受客户端HTTP协议升级的证明。


    • WebSocket建立连接

    客户端接收服务器的响应后,同样会解析请求头信息,取出请求信息中的Sec-WebSocket-Accept字段,并用服务器内部处理Sec-WebSocket-Key字段的算法处理之前发送的Sec-WebSocket-Key,把处理得到的结果与Sec-WebSocket-Accept进行对比,数据相同则表示客户端与服务器成功建立WebSocket连接,反之失败。


    ####WebSocket通信协议的数据传输

    WebSocket通讯协议的数据传输格式是以帧的形式传输的,其帧格式如图:

    这里写图片描述

    相关字段说明:

    这里写图片描述

    根据数据帧的设计可以看出,WebSocket通讯协议是通过心跳检查PING-PONG帧来实现WebSocket长连接。当WebSocket连接建立后,PING帧和PONG帧都会不携带数据地进行来回传输,当连接发生变化时,相应数据信息会被植入PING帧,PONG帧作为响应帧返回结果。

    建立连接后,可在客户端使用JavaScript实现相关的WebSocket API。相关实现接口如下表:

    实现方式说明
    New WebSocket(“ws://host:port/”);发起与服务器建立WebSocket连接的对象
    websocket.onopen()=function(){}接收连接成功建立的响应函数
    websocket.onerror()=function(){}接收异常信息的响应函数
    websocket.onmessage()=functionm(event){}接收服务器返回的消息函数
    websocket.onclose()=function(){}接收连接关闭的响应函数
    sendMessage(event.data)=function(data){}发送消息函数
    websocket.close()=function(){}连接关闭函数

    从目前各Web技术领域来看,各大浏览器均支持WebSocket协议的实现,但WebSocket协议所支持服务器较少,如:NodeJS、Tomcat7.0等,相信未来会得到普及。


    ####Spring-WebSocket源码剖析

    上面已经通过解析数据包阐述了WebSocket的基本原理。由于Spring 4.0更新后直接增加了对WebSocket的支持,下文将借此通过Spring 框架提供的源代码进行分析Spring 框架是如何实现在接收到客户端的连接请求后与服务器建立连接的。

    以下代码片段摘自官方下载的源代码:spring-websocket-4.0.6.RELEASE-sources
    下载地址:https://spring.io/blog/2014/07/08/spring-framework-4-0-6-released

    (1)封装来自客户端发送的WebSocket连接请求信息:

    public class WebSocketHttpHeaders extends HttpHeaders {
    
    	public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";
    
    	public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions";
    
    	public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
    
    	public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
    
    	public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
    
    	private static final long serialVersionUID = -6644521016187828916L;
    
    	private final HttpHeaders headers;
    
    	public WebSocketHttpHeaders() {
    		this(new HttpHeaders(), false);
    	}
    
    	public WebSocketHttpHeaders(HttpHeaders headers) {
    		this(headers, false);
    	}
    
    	private WebSocketHttpHeaders(HttpHeaders headers, boolean readOnly) {
    		this.headers = readOnly ? HttpHeaders.readOnlyHttpHeaders(headers) : headers;
    	}
    
    	public static WebSocketHttpHeaders readOnlyWebSocketHttpHeaders(WebSocketHttpHeaders headers){
    		return new WebSocketHttpHeaders(headers, true);
    	}
    
    	***
    	**
    	*
    	*
    }
    
    

    (2)判断请求的协议类型,获取请求信息进入握手环节: 注意相关注释!!

    public abstract class AbstractWebSocketClient implements WebSocketClient {
    
    	protected final Log logger = LogFactory.getLog(getClass());
    
    	private static final Set<String> specialHeaders = new HashSet<String>();
    
    	static {
    		specialHeaders.add("cache-control");
    		specialHeaders.add("connection");
    		specialHeaders.add("host");
    		specialHeaders.add("sec-websocket-extensions");
    		specialHeaders.add("sec-websocket-key");
    		specialHeaders.add("sec-websocket-protocol");
    		specialHeaders.add("sec-websocket-version");
    		specialHeaders.add("pragma");
    		specialHeaders.add("upgrade");
    	}
    
    	@Override
    	public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,String uriTemplate, Object... uriVars) {..
    
    	@Override
    	public final ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
    			WebSocketHttpHeaders headers, URI uri) {
    
    		Assert.notNull(webSocketHandler, "webSocketHandler must not be null");
    		Assert.notNull(uri, "uri must not be null");
    
    		String scheme = uri.getScheme();
    		
    		/*判断协议类型*/
    		Assert.isTrue(((scheme != null) && ("ws".equals(scheme) || "wss".equals(scheme))), "Invalid scheme: " + scheme);
    
    		if (logger.isDebugEnabled()) {
    			logger.debug("Connecting to " + uri);
    
            /*封装响应信息*/
    		HttpHeaders headersToUse = new HttpHeaders();
    		if (headers != null) {
    			for (String header : headers.keySet()) {
    				if (!specialHeaders.contains(header.toLowerCase())) {
    					headersToUse.put(header, headers.get(header));
    				}
    			}
    		}
    
    		List<String> subProtocols = ((headers != null) && (headers.getSecWebSocketProtocol() != null)) ?
    				headers.getSecWebSocketProtocol() : Collections.<String>emptyList();
    
    		List<WebSocketExtension> extensions = ((headers != null) && (headers.getSecWebSocketExtensions() != null)) ?
    				headers.getSecWebSocketExtensions() : Collections.<WebSocketExtension>emptyList();
    				
            /*进入握手环节*/
    		return doHandshakeInternal(webSocketHandler, headersToUse, uri, subProtocols, extensions,
    				Collections.<String, Object>emptyMap());
    	}
    
    	protected abstract ListenableFuture<WebSocketSession> doHandshakeInternal(WebSocketHandler webSocketHandler,
    			HttpHeaders headers, URI uri, List<String> subProtocols, List<WebSocketExtension> extensions,
    			Map<String, Object> attributes);
    
    }
    

    (3)握手处理: 注意相关注释!!

    public class StandardWebSocketClient extends AbstractWebSocketClient {
    
    	private final WebSocketContainer webSocketContainer;
    
    	private AsyncListenableTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
    
    	public StandardWebSocketClient() {
    		this.webSocketContainer = ContainerProvider.getWebSocketContainer();
    	}
    
    	public StandardWebSocketClient(WebSocketContainer webSocketContainer) {
    		Assert.notNull(webSocketContainer, "WebSocketContainer must not be null");
    		this.webSocketContainer = webSocketContainer;
    	}
    
    	public void setTaskExecutor(AsyncListenableTaskExecutor taskExecutor) {..
    
    	public AsyncListenableTaskExecutor getTaskExecutor() {..
    
        /*握手处理,生成连接会话句柄Session*/
    	@Override
    	protected ListenableFuture<WebSocketSession> doHandshakeInternal(WebSocketHandler webSocketHandler,
    			HttpHeaders headers, final URI uri, List<String> protocols,
    			List<WebSocketExtension> extensions, Map<String, Object> attributes) {
    
    		int port = getPort(uri);
    		InetSocketAddress localAddress = new InetSocketAddress(getLocalHost(), port);
    		InetSocketAddress remoteAddress = new InetSocketAddress(uri.getHost(), port);
    
    		final StandardWebSocketSession session = new StandardWebSocketSession(headers,
    				attributes, localAddress, remoteAddress);
    
    		final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create();
    		configBuilder.configurator(new StandardWebSocketClientConfigurator(headers));
    		configBuilder.preferredSubprotocols(protocols);
    		configBuilder.extensions(adaptExtensions(extensions));
    		final Endpoint endpoint = new StandardWebSocketHandlerAdapter(webSocketHandler, session);
    
    		Callable<WebSocketSession> connectTask = new Callable<WebSocketSession>() {
    			@Override
    			public WebSocketSession call() throws Exception {
    				webSocketContainer.connectToServer(endpoint, configBuilder.build(), uri);
    				return session;
    			}
    		};
    
    		if (this.taskExecutor != null) {
    			return this.taskExecutor.submitListenable(connectTask);
    		}
    		else {
    			ListenableFutureTask<WebSocketSession> task = new ListenableFutureTask<WebSocketSession>(connectTask);
    			task.run();
    			return task;
    		}
    	}
    
    	private static List<Extension> adaptExtensions(List<WebSocketExtension> extensions){..
    
    	private InetAddress getLocalHost() {..
    	private int getPort(URI uri) {..
    	private class StandardWebSocketClientConfigurator extends Configurator {
    		private final HttpHeaders headers;
    		public StandardWebSocketClientConfigurator(HttpHeaders headers) {..
    		@Override
    		public void beforeRequest(Map<String, List<String>> requestHeaders) {..
    		@Override
    		public void afterResponse(HandshakeResponse response) {..
    
    }
    

    (4)建立WebSocket连接,开始通讯: 注意相关注释!!

    public abstract class ConnectionManagerSupport implements SmartLifecycle {
    
    	protected final Log logger = LogFactory.getLog(getClass());
    
    	private final URI uri;
    
    	private boolean autoStartup = false;
    
    	private boolean isRunning = false;
    
    	private int phase = Integer.MAX_VALUE;
    
    	private final Object lifecycleMonitor = new Object();
    
    	public ConnectionManagerSupport(String uriTemplate, Object... uriVariables) {
    		this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(
    				uriVariables).encode().toUri();
    	}
    
    	public void setAutoStartup(boolean autoStartup) {..
    	@Override
    	public boolean isAutoStartup() {..
    	public void setPhase(int phase) {..
    	@Override
    	public int getPhase() {..
    	protected URI getUri() {..
    	@Override
    	public boolean isRunning() {..
    
    	@Override
    	public final void start() {
    		synchronized (this.lifecycleMonitor) {
    			if (!isRunning()) {
    				startInternal();
    			}
    		}
    	}
    
        /*打开连接,synchronized处理并发连接*/
    	protected void startInternal() {
    		synchronized (lifecycleMonitor) {
    			if (logger.isDebugEnabled()) {
    				logger.debug("Starting " + this.getClass().getSimpleName());
    			}
    			this.isRunning = true;
    			openConnection();
    		}
    	}
    
    	protected abstract void openConnection();
    
    	@Override
    	public final void stop() {..
    
        /*关闭连接*/
    	protected void stopInternal() throws Exception {
    		if (isConnected()) {
    			closeConnection();
    		}
    	}
    
    	protected abstract boolean isConnected();
    
    	protected abstract void closeConnection() throws Exception;
    
    	@Override
    	public final void stop(Runnable callback) {
    		synchronized (this.lifecycleMonitor) {
    			this.stop();
    			callback.run();
    		}
    	}
    
    }
    

    Spring-WebSocket领域模型:

    这里写图片描述


    今天不卖奶茶了~

    展开全文
  • 引言  随着通信网络规模的不断扩大,通信电源的数量也在不断地增多,对通信电源的...Web技术功能比较完善,性能优越,但是基于Web通信的监控系统需要铺设网络线路,针对于分散点较多的情况需要使用多个Web站点,费
  • 自从HTML和Mosaic浏览器问世以来,Web平台已成为跨越终端...以HTML5为代表的Web浏览器的技术升级浪潮,以及正在进行的将实时通信功能融入Web平台的工作,为通信与数据的结合以及改善用户体验创造了新的机遇(见图1)。
  • Web实时通讯技术简介

    千次阅读 2016-08-08 09:54:04
    一、概述 1.Web端即时通讯技术 即时通讯技术简单的说...这种限制出现的主要原因是,一般的Web通信都是浏览器先发送请求到服务器,服务器再进行响应完成数据的现实更新。 2.实现Web端即时通讯的方法 实现即时通讯
  • 在介绍web实时交互方式前,先了解一下HTTP的通信机制,共分为以下四步: 第一步: HTTP是基于传输层的TCP协议,在HTTP开始工作之前,web浏览器首先通过网络与尾部服务器在TCP层建立连接。在TCP层进行“三次握手”之后...
  • Web 实时推送技术的总结

    千次阅读 2019-03-14 08:27:57
    以上我们介绍了三种实时推送技术,然而各自的缺点很明显,使用起来并不理想,接下来我们着重介绍另一种技术–websocket,它是比较理想的双向通信技术。 二、WebSocket 1.什么是websocket WebSocket是一种全新的...
  • Web端即时通信技术

    千次阅读 2018-06-15 10:48:24
    以前限制web开发接近原生应用的一大障碍就是web开发很难实现即时通信。因为在web应用中,由于其使用的http协议的特殊性,只有用户在界面中进行操作后,服务器的到客户端的信息,才能进行响应。也就是说,使用web开发...
  • WebSocket实现实时通信

    万次阅读 多人点赞 2021-07-23 14:31:31
    WebSocket 是一种数据通信协议,也是用于客户端和服务端数据通信,类似于我们常见的 http 既然有 http,为啥还要 WebSocket http 通信是单向的 请求 + 响应 没有请求也就没有响应 初次接触 WebSocket 的人,都会问...
  • 引言  随着通信网络规模的不断扩大,通信电源的数量也在不断地增多,对通信电源的...Web技术功能比较完善,性能优越,但是基于Web通信的监控系统需要铺设网络线路,针对于分散点较多的情况需要使用多个Web站点,费
  • web实时通信讲H5 WebSocket

    千次阅读 2017-04-25 13:03:03
    传统的B/S通信通常我们打开一个浏览器访问网页时,都会向页面所在的服务器发送一个HTTP请求,然后web服务器确认请求并向浏览器做出响应。简单的说,就是一个请求对应的一个响应。然而这种方法对许多的应用场景都会使...
  • 基于 HTTP 的实时 Web 通信

    千次阅读 2018-03-27 15:27:24
    基于 HTTP 的实时 Web 通信 Web 应用的信息交互过程通常是客户端通过浏览器发出一个请求,服务器端接收和审核完请求后进行处理并返回结果给客户端,然后客户端浏览器将信息呈现出来,这种机制对于信息变化不是特别...
  • 讲解如何搭建Janus,并运行Janus WebDemo。 实现iOS端使用Janus流媒体服务器与Web端进行多人的实时音视频互动 课程升级支持janus0.9.2,配置文档已经上传在  《janus-demo演示》附件中
  • Web页面实时刷新技术探讨

    万次阅读 2017-09-05 11:01:49
    随着网络技术的飞速发展,使用B/S结构来实现项目应用已经越来越多,而实时监控一直都是多数行业软件所必备的功能,由此使用Web页面来实现实时监控成了一种必然的需求。   二、实时刷新技术 1、传统的页面...
  • WEB服务器端技术

    千次阅读 2022-04-11 17:39:07
    WEB服务器端技术 客户端是与用户交互的唯一接口,对于软件测试人员来说不可掉以轻心,那么服务器端又需要我们了解哪些技术呢? 事实上,对于Web系统来说,相比于客户端技术,服务器端技术更是深不可测,其各类技术...
  • 了解web世界中, 各种网络通信协议, 实现的手段, 区别和原理. 另外发现, 网上很多博客, 问答等都没有回答清楚web中的一些协议, 通信的准确定义以及原理和实质.这里希望自己能逐步准确整理出来网络相关的概念,协议, ...
  • sockjs-web实时通信协议

    万次阅读 2015-06-22 15:12:12
    sockjs-web实时通讯应用解决方案 socksjs目标 客户端和服务器端api尽可能简洁,尽量靠近websocket api 支持服务端扩展和负载均衡技术 传输层应该全面支持跨域通信 如果收到代理服务器的限制,传输层能优雅地从...
  • 实时通信技术的架构设计

    千次阅读 2015-03-20 13:55:15
    一、WEB实时通信技术对比 在WEB端的实时通信技术中,主要有以下几种方式:   1)轮询技术 轮询是最简单的一种实时通信技术,易于实现,非常适用于一些小型的应用。其基本原理是这样的,先在客户端设定...
  • Web应用应用基础—数据流通信过程 **场景:**当你从开始在浏览器输入网站名称/网址(URL)到浏览器显示出你需要看到内容的时候,这个过程是怎么实现的呢,了解整个通信过程有利于学习Web安全中漏洞原理的理解。 大致...
  • 基于WEB实时通信方案

    千次阅读 2015-03-20 18:20:30
    本文所讲述的『实时通信』主要围绕浏览器端和服务器端之间的实时通信实时通信主要分3大类: 1. Pull技术,轮询(Polling) 客户端定时轮询请求,服务器端立刻返回。 优点:短链接,服务器处理方便,...
  • WEB通信交互的几种方式

    千次阅读 2019-02-17 00:40:32
    WEB通信交互的几种方式 - 实时通信发展过程简介  简单介绍一下现在的WEB通信有以下几种方式:最基本的http请求方式,Ajax轮询,Ajax长轮询,HTML5推送事件,HTML5的WebSocket。 最基本的http请求方式: 客户端...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 287,587
精华内容 115,034
关键字:

web实时通信技术