服务器推技术实现

技术文档网 2021-04-15

ajax定时请求

通过ajax异步请求,并每隔一段时间请求一次如setInterval或客户端循环请求等

setInterval(function() {
    $.ajax({
        url: 'http://localhost:8080/demo.php',
        success: function() {
                //code from here
        }
    });
}, 3000);

长连接(AJAX Long-Polling)

浏览器通过ajax异步发请求到服务端,服务端阻碍请求,直到有数据返回给浏览器商为止,客户端收到数据或是出现错误时进行相应的处理并重新请求服务端建立连接

comet-iframe

客户端

<script type="text/javascript">
var comet = {
  connection   : false,
  iframediv    : false,

  initialize: function() {
    if (navigator.appVersion.indexOf("MSIE") != -1) {

      // For IE browsers
      comet.connection = new ActiveXObject("htmlfile");
      comet.connection.open();
      comet.connection.write("<html>");
      comet.connection.write("<script>document.domain = '"+document.domain+"'");
      comet.connection.write("</html>");
      comet.connection.close();
      comet.iframediv = comet.connection.createElement("div");
      comet.connection.appendChild(comet.iframediv);
      comet.connection.parentWindow.comet = comet;
      comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='./comet/backend.php'></iframe>";

    } else if (navigator.appVersion.indexOf("KHTML") != -1) {

      // for KHTML browsers
      comet.connection = document.createElement('iframe');
      comet.connection.setAttribute('id',     'comet_iframe');
      comet.connection.setAttribute('src',    './comet/backend.php');
      with (comet.connection.style) {
        position   = "absolute";
        left       = top   = "-100px";
        height     = width = "1px";
        visibility = "hidden";
      }
      document.body.appendChild(comet.connection);

    } else {

      // For other browser (Firefox...)
      comet.connection = document.createElement('iframe');
      comet.connection.setAttribute('id',     'comet_iframe');
      with (comet.connection.style) {
        left       = top   = "-100px";
        height     = width = "1px";
        visibility = "hidden";
        display    = 'none';
      }
      comet.iframediv = document.createElement('iframe');
      comet.iframediv.setAttribute('src', './comet/backend.php');
      comet.connection.appendChild(comet.iframediv);
      document.body.appendChild(comet.connection);

    }
  },

  // this function will be called from comet/backend.php
  printServerTime: function (time) {
    $('content').innerHTML = time;
  },

  onUnload: function() {
    if (comet.connection) {
      comet.connection = false; // release the iframe to prevent problems with IE when reloading the page
    }
  }
}
Event.observe(window, "load",   comet.initialize);
Event.observe(window, "unload", comet.onUnload);
</script>

服务端

<?php

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
flush();

?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Comet php backend</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<script type="text/javascript">
  // KHTML browser don't share javascripts between iframes
  var is_khtml = navigator.appName.match("Konqueror") || navigator.appVersion.match("KHTML");
  if (is_khtml)
  {
    var prototypejs = document.createElement('script');
    prototypejs.setAttribute('type','text/javascript');
    prototypejs.setAttribute('src','prototype.js');
    var head = document.getElementsByTagName('head');
    head[0].appendChild(prototypejs);
  }
  // load the comet object
  var comet = window.parent.comet;
</script>

<?php

while(1) {
  echo '<script type="text/javascript">';
  echo 'comet.printServerTime('.time().');';
  echo '</script>';
  flush(); // used to send the echoed data to the client
  sleep(1); // a little break to unload the server CPU
}

?>


</body>
</html>

comet-ajax

客户端

<script type="text/javascript">
var Comet = Class.create();
Comet.prototype = {

  timestamp: 0,
  url: './backend.php',
  noerror: true,

  initialize: function() { },

  connect: function()
  {
    this.ajax = new Ajax.Request(this.url, {
      method: 'get',
      parameters: { 'timestamp' : this.timestamp },
      onSuccess: function(transport) {
        // handle the server response
        var response = transport.responseText.evalJSON();
        this.comet.timestamp = response['timestamp'];
        this.comet.handleResponse(response);
        this.comet.noerror = true;
      },
      onComplete: function(transport) {
        // send a new ajax request when this request is finished
        if (!this.comet.noerror)
          // if a connection problem occurs, try to reconnect each 5 seconds
          setTimeout(function(){ comet.connect() }, 5000);
        else
          this.comet.connect();
        this.comet.noerror = false;
      }
    });
    this.ajax.comet = this;
  },

  disconnect: function()
  {
  },

  handleResponse: function(response)
  {
    $('content').innerHTML += '<div>' + response['msg'] + '</div>';
  },

  doRequest: function(request)
  {
    new Ajax.Request(this.url, {
      method: 'get',
      parameters: { 'msg' : request }
    });
  }
}
var comet = new Comet();
comet.connect();
</script>

服务端

<?php

header("Content-Type: text/event-stream\n\n");
$filename  = dirname(__FILE__).'/data.txt';

// store new message in the file
$msg = isset($_GET['msg']) ? $_GET['msg'] : '';
if ($msg != '')
{
  file_put_contents($filename,$msg);
  die();
}

// infinite loop until the data file is not modified
$lastmodif    = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
$currentmodif = filemtime($filename);
while ($currentmodif <= $lastmodif) // check if the data file has been modified
{
  usleep(10000); // sleep 10ms to unload the CPU
  clearstatcache();
  $currentmodif = filemtime($filename);
}

// return a json array
$response = array();
$response['msg']       = file_get_contents($filename);
$response['timestamp'] = $currentmodif;
echo json_encode($response);
flush();

?>

html5的WebSocket

WebSocket可以实现与客户端与服务器双向,基于消息的文本或二进制数据通信,主要包括两个部分,客户端WebSocket API及WebSocket协议。

WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)首先HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已.

WebSocket API

var connection = new WebSocket('ws://localhost:8080');

// When the connection is open, send some data to the server
connection.onopen = function () {
  connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
  console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
  console.log('Server: ' + e.data);
};

WebSocket 协议

WebSocket通信协议包含两个部分,一是开放性HTTP握手连接协商连接参数,二是二进制消息分帧机制(接收消息的文本和二进制数据传输)。它是一个独立完善的协议,也可以在浏览器之外实现

请求头信息

Connection:Upgrade
Sec-WebSocket-Key:eDCPPyPQZq7PiwRcx8SPog==
Sec-WebSocket-Version:13
Upgrade:websocket

响应头信息

HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Sec-WebSocket-Accept:/ZVAP3n6XuqDUoDp416PYUO+ZJc=
Upgrade:websocket

优点

*真正的全双工通信 *支持跨域设置(Access-Control-Allow-Origin)

缺点

  • 采用新的协议,后端需要单独实现
  • 客户端并不是所有浏览器都支持
  • 代理服务器会有不支持websocket的情况
  • 无超时处理
  • 更耗电及占用资源
  • TIP 代理、很多现有的HTTP中间设备可能不理解新的WebSocket协议,而这可能导致各种问题,使用时需要注意,可以使借助TLS,通过建立一条端到端的加密信道,可以让WebSocket通信绕过所有中间代理。

HTML5 服务器发送事件(server-sent event)

Server-sent-events(SSE)让服务端可以向客户端流式发送文本消息,在实现上,客户端浏览器中增加EventSource对象,使其能通过事件的方式接收到服务器推送的消息,在服务端,使用长连接的事件流协议,即请求响应时增加新数据流数据格式。

EventSource API

var source = new EventSource('http://localhost:8080');

source.addEventListener('message', function(e) {
  console.log(e.data);
}, false);

source.addEventListener('open', function(e) {
  // Connection was opened.
}, false);

source.addEventListener('error', function(e) {
  if (e.readyState == EventSource.CLOSED) {
    // Connection was closed.
  }
}, false);

source.addEventListener('userlogin', function(e) {
  console.log(e.data);
}, false);

EventSource接口还能自动重新连接并跟踪最近接收的消息,还可以向服务器发送上一次接收到消息的ID,以便服务器重传丢失的消息并恢复流。

Event Stream协议

SSE事件流以流式HTTP响应请求,客户端发起普通的HTTP请求,服务器以自定义的text/event-stream内容类型响应,然后通过事件传递数据。

响应头

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

响应数据格式

id: 123\n
retry: 10000\n
event: userlogin\n
data: {"username": "John123"}\n\n
  1. “Content-Type: text/event-stream”是专门为SSE设计的MIME类型`
  2. retry可以定义推送间隔,如果不发送这个指令,默认间隔5000毫秒`
  3. 每行指令后面要有换行 \n ,用php的请用兼容方案 PHP_EOL`
  4. 最后一条指令要两个换行`
  5. 事件ID,会成为当前EventSource对象的内部属性"最后一个事件ID"的属性值.
  6. data,消息的数据字段.如果该条消息包含多个data字段,则客户端会用换行符把它们连接成一个字符串来作为字段值.

优点

  • 基于现有http协议,实现简单
  • 断开后自动重联,并可设置重联超时
  • 派发任意事件
  • 跨域并有相应的安全过滤

缺点

  • 只能单向通信,服务器端向客户端推送事件
  • 事件流协议只能传输UTF-8数据,不支持二进制流。
  • IE下目前所有不支持EventSource
  • Tip 如果代理服务器或中间设备不支持SSE,会导致连接失效,正式环境中使用通过TLS协议发送SSE事件流。

客户端

<div id="result"></div>

<script>
if(typeof(EventSource)!=="undefined")
{
    var source=new EventSource("http://localhost:8010/test.php");
    source.onmessage=function(event)
    {
        document.getElementById("result").innerHTML+=event.data + "<br />";
    };
}
else
{
    document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
}

服务端

<?php
date_default_timezone_set("America/New_York");
header("Content-Type: text/event-stream\n\n");
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');

$counter = rand(1, 10);
while (1) {
// Every second, sent a "ping" event.
//
    echo "event: ping\n";
    $curDate = date(DATE_ISO8601);
    echo 'data: {"time": "' . $curDate . '"}';
    echo "\n\n";

    // Send a simple message at random intervals.

    $counter--;

    if (!$counter) {
        echo 'data: This is a message at time ' . $curDate . "\n\n";
        $counter = rand(1, 10);
    }

    ob_flush();
    flush();
    sleep(1);
}

相关文章

  1. Web服务器缓存

    一、URL映射 对于任何的web服务器,当我们向他发送一个http请求后,它要做的主要工作就是解析URL,然后完成从URL到实际内容或资源的映射。这里所说的映射是一个抽象概念,实际上就是服务器处理请

  2. 静态资源加载

    在模块化开发模式下,静态资源需要程序自己收集,因此为一个单独页面指定特定的静态资源加载的方式已经不能满足了。 针对于单个请求,最优的加载方案是实时收集需要的模块静态资源并输出,不会浪费带宽。但是,一旦

  3. 服务器推技术实现

    ajax定时请求 通过ajax异步请求,并每隔一段时间请求一次如setInterval或客户端循环请求等 setInterval(function() { $.ajax({ u

  4. 移动端滚动穿透问题

    定义:移动端弹出fixed弹窗的话,在弹窗上滑动会导致下层的页面跟着滚动,这个叫 “滚动穿透” 解决方案1 在弹出层的touchstart事件中调用preventDefault 这种方法不可取,至少有

  5. 移动端屏幕尺寸与像素密度

    一、英寸与厘米转换 1 inch = 2.54cm = 25.4mm 二、像素间距 像素(Pixels)是组成数字图像的最小单元,即一个一个彩色的颜色点。 一般人都以为像素是一个个的小圆点,实际上

随机推荐

  1. Web服务器缓存

    一、URL映射 对于任何的web服务器,当我们向他发送一个http请求后,它要做的主要工作就是解析URL,然后完成从URL到实际内容或资源的映射。这里所说的映射是一个抽象概念,实际上就是服务器处理请

  2. 静态资源加载

    在模块化开发模式下,静态资源需要程序自己收集,因此为一个单独页面指定特定的静态资源加载的方式已经不能满足了。 针对于单个请求,最优的加载方案是实时收集需要的模块静态资源并输出,不会浪费带宽。但是,一旦

  3. 服务器推技术实现

    ajax定时请求 通过ajax异步请求,并每隔一段时间请求一次如setInterval或客户端循环请求等 setInterval(function() { $.ajax({ u

  4. 移动端滚动穿透问题

    定义:移动端弹出fixed弹窗的话,在弹窗上滑动会导致下层的页面跟着滚动,这个叫 “滚动穿透” 解决方案1 在弹出层的touchstart事件中调用preventDefault 这种方法不可取,至少有

  5. 移动端屏幕尺寸与像素密度

    一、英寸与厘米转换 1 inch = 2.54cm = 25.4mm 二、像素间距 像素(Pixels)是组成数字图像的最小单元,即一个一个彩色的颜色点。 一般人都以为像素是一个个的小圆点,实际上