12bet,的读书笔记

第14章:浏览器网络概述

12bet,高层浏览器网络api,协议和服务

高层浏览器网络api,协议和服务

自动管理的套接字池在所有浏览器进程间共享

套接字池

套接字池规模一般都是6个套接字

自动化的套接字池会自动充用TCP连接,从而保障性能,此外,这种架构的优点有:

  • 12博体育,浏览器可以按照优先次序发送排队的请求
  • 浏览器可以重用套接字以最小化延迟并提升吞吐量
  • 浏览器可以预测请求提前打开套接字
  • 12博体育,浏览器可以优化和事关闭空闲的套接字
  • 浏览器可以优化分配给所有套接字的带宽

xhr,sse和websocket的对比

API XHR SSE WebSocket
请求流
响应流 受限
分帧机制 HTTP 事件流 二进制分帧
二进制数据传输 否(base64)
压缩 受限
应用传输协议 HTTP HTTP WebSocket
网络传输协议 TCP TCP TCP

第15章:XHR

15.1 XHR简史

XHR2新增:

  • 支持请求超时
  • 支持传输二进制和文本数据
  • 支持应用重写媒体类型和编码响应
  • 支持监控每个请求的进度事件
  • 支持有效的文件上传
  • 支持安全的跨来源请求

15.2 CORS

服务器配置Access-Control-Allow-Origin,但是这个请求有以下限制:

  • 省略 cookie 和 HTTP 认证等用户凭据
  • 只能发送“简单的请求”(GET,POST,HEAD)

要启用 cookie 和 HTTP 认证,客户端发器请求是要加上withCredentials属性,而且服务器必须以 Access-Control-Allow-Origin首部响应,表示允许用户发送隐私数据。

如果客户端需要写或者读自定义的 HTTP 首部,或者想要使用“不简单的方法”发送请求,那么首先要获得服务器的许可,即先向第三方服务器发送一个 preflight 请求:

preflight 请求

OPTIONS /resource.js HTTP/1.1  // OPTIONS 请求(协商支持的方法)
Host: thirdparty.com
Origin: http://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: My-Custom-Header
...

preflight 响应

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET, POST, PUT    // 告诉客户端支持 GET, POST, PUT
Access-Control-Allow-Headers: My-Custom-Header  // 告诉客户端支持 My-Custom-Header
...

协商之后才会正式发送跨域请求

15.3 12bet,通过XHR下载

12bet,浏览器可以自动解码的数据类型:

  • ArrayBuffer:二进制数据缓冲区
  • Blob:二进制大对象或不可变数据
  • Document:HTML或XML
  • JSON
  • Text
var xhr = new XMLHttpRequest();
xhr.open('GET', '/images/photo.webp');
xhr.responseType = 'blob';
xhr.onload = function() {
  if (this.status == 200) {
    var img = document.createElement('img');
    img.src = window.URL.createObjectURL(this.response);
    img.onload = function() {
        window.URL.revokeObjectURL(this.src);
    }
    document.body.appendChild(img);
  }
};
xhr.send();

15.4 通过XHR上传

send()方法可以接受DOMStringDocumentFormDataBlobFileArrayBuffer对象,自动完成相应的编码,并设置适当的内容类型(Content-Type),然后再分派请求

Blob可以分块发送:

var blob = new Blob(['sample content']);
const BYTES_PER_CHUNK = 1024 * 1024;
const SIZE = blob.size;
var start = 0;
var end = BYTES_PER_CHUNK;
while(start < SIZE) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload');
  xhr.onload = function() { ... };
  // 告诉服务器上传的数据范围(开始位置-结束位置/总大小)
  xhr.setRequestHeader('Content-Range', start+'-'+end+'/'+SIZE);
  xhr.send(blob.slice(start, end));
  start = end;
  end = start + BYTES_PER_CHUNK;
}

15.5 监控下载和上传进度

XHR进度事件:

事件类型 说明 触发次数
loadstart 传输开始 一次
progress 正在传输 零或多次
error 传输出错 零或多次
abort 传输终止 零或多次
load 传输成功 零或多次
loadend 传输完成 一次
var xhr = new XMLHttpRequest();
xhr.open('GET','/resource');
xhr.timeout = 5000;
xhr.addEventListener('load', function() { ... });
xhr.addEventListener('error', function() { ... });
var onProgressHandler = function(event) {
  if(event.lengthComputable) {
    var progress = (event.loaded / event.total) * 100;
... }
}
// 上传进度
xhr.upload.addEventListener('progress', onProgressHandler);
// 下载进度
xhr.addEventListener('progress', onProgressHandler);
xhr.send();

15.6 通过XHR实现流式数据传输

var xhr = new XMLHttpRequest();
xhr.open('GET', '/stream');
xhr.seenBytes = 0;
xhr.onreadystatechange = function() {
  if(xhr.readyState > 2) {
    // 获取新数据
    var newData = xhr.responseText.substr(xhr.seenBytes);
    // 处理新数据
    // ...
    // 更新处理的字节偏移量
    xhr.seenBytes = xhr.responseText.length;
  }
};
xhr.send();

基于XHR的流实现麻烦,效率很低,而且缺乏规范,总之不适合用于实现流式数据处理

第16章:SSE

API:

var source = new EventSource("/path/to/stream-url");
source.onopen = function () { ... };
source.onerror = function () { ... };
source.addEventListener("foo", function (event) {
  processFoo(event.data);
});
source.onmessage = function (event) {
  log_message(event.id, event.data);
  if (event.id == "CLOSE") {
    source.close();
  }
};

第17章:WebSocket

17.1 WebSocket API

var ws = new WebSocket('wss://example.com/socket');
ws.onerror = function (error) { ... }
ws.onclose = function () { ... }
ws.onopen = function () {
  ws.send("Connection established. Hello server!");
};
ws.onmessage = function(msg) {
  if(msg.data instanceof Blob) {
    processBlob(msg.data);
  } else {
    processText(msg.data);
  }
};

接收文本和二进制数据

var ws = new WebSocket('wss://example.com/socket');
// 如果接收二进制数据,强制转换为``ArrayBuffer``
ws.binaryType = "arraybuffer";
ws.onmessage = function(msg) {
  if(msg.data instanceof ArrayBuffer) {
    processArrayBuffer(msg.data);
  } else {
    processText(msg.data);
  }
};

发送文本和二进制数据

var ws = new WebSocket('wss://example.com/socket');
ws.onopen = function () {
  // 发送文本
  socket.send("Hello server!");
  // 发送json
  socket.send(JSON.stringify({'msg': 'payload'}));
  // 发送二进制ArrayBuffer
  var buffer = new ArrayBuffer(128);
  socket.send(buffer);
  // 发送二进制ArrayBufferView
  var intview = new Uint32Array(buffer);
  socket.send(intview);
  // 发送Blob
  var blob = new Blob([buffer]);
  socket.send(blob);
}

子协议协商

// 告诉服务器客户端支持 appProtocol, appProtocol-v2 协议
var ws = new WebSocket('wss://example.com/socket', ['appProtocol', 'appProtocol-v2']);
ws.onopen = function () {
  // 判断服务器是不是使用的 appProtocol-v2
  if (ws.protocol == 'appProtocol-v2') {
    // ...
  } else {
    // ...
  }
}

17.2 WebSocket 协议

websocket frame

队首阻塞:websocket容易发生队首阻塞,因为消息可能被分成一或多个帧,但是不同消息的帧不能交错发送,因为没有与http 2.0分帧机制中“流ID”对等的字段。

websocket不支持多路复用,所以每个websocket连接都需要一个专门的TCP连接

协议拓展:

  • 多路复用
  • 压缩

HTTP升级协商的首部

  • Sec-WebSocket-Version:客户端发送,想使用的websocket协议版本
  • Sec-WebSocket-Key:客户端发送,自动生成的键,验证服务器支持请求的协议版本
  • Sec-WebSocket-Accept:服务器响应,包含Sec-WebSocket-Key的签名值,证明它支持的请求的协议版本
  • Sec-WebSocket-Protocol:用于协商应用子协议:客户端发送支持的协议列表,服务器必须只回应一个协议名
  • Sec-WebSocket-Extensions:用于协商本次连接要使用的websocket拓展:客户端发送支持的拓展,服务器通过返回相同的首部确认自己支持一或多个拓展

17.3 WebSocket使用场景和性能

xhr,sse和websocket比较

17.4 性能检查表

主要有以下要点:

  • 使用安全websocket实现可靠的部署
  • 密切关注脚本性能
  • 利用子协议确定应用协议
  • 优化二进制净荷以最小化传输数据
  • 考虑压缩utf-8内容以最小化传输数据
  • 设置正确的二进制类型以接收净荷
  • 监控客户端缓冲数据量
  • 切分应用消息避免队首阻塞
  • 合用的情况下利用其它传输机制