⽹络协议-HTTP性能优化(Ajax、WebSocket、HTTP2.0)
从 Ajax 到 WebSocket
背景
在建⽴ HTTP 标准规范的时候,设计者的初衷主要是想把 HTTP 当做传输静态 HTML ⽂档的协议,但是随着互联⽹的发展,Web 应⽤的⽤途更加多样性,逐渐诞⽣了电商⽹站(如淘宝、亚马逊)、社交⽹络(如Facebook、Twitter)等功能更加复杂的应⽤,这些⽹站的功能单纯靠静态 HTML 显然是实现不了的,因此⼜产⽣了通过 CGI 将 Web 服务器与后台动态应⽤连接起来,从⽽通过后台脚本语⾔实现的应⽤驱动⽹站功能,这些脚本语⾔包括 PHP、Python、Ruby、Node、JSP、ASP 等,通过这种⽅式虽然解决了 Web 应⽤的功能扩展问题,但是 HTTP 协议本⾝的限制和性能问题却没有得到有效解决。
HTTP 功能和性能上的问题虽然可以通过创建⼀套新的协议来彻底解决,但是⽬前基于 HTTP 的服务端和客户端应⽤遍布全球,完全抛弃不太现实,但问题却要解决,因此,诞⽣了很多基于 HTTP 协议的新技术和新协议来补⾜ HTTP 协议本⾝的缺陷。
Ajax
随着⽹站功能的复杂,对资源实时性的要求也越来越⾼,但是 HTTP 本⾝⽆法做到实时显⽰服务器端更新
的内容,要获取服务器端的最新内容,就得频繁从客户端发起新的请求(⽐如刷新页⾯),如果服务器上没有更新,就会造成通信的浪费,⽽且从⽤户体验来说也不够友好。
为了解决这个问题,诞⽣了 Ajax 技术,其全称是 Asynchronous JavaScript And XML,即异步 Javascript 与 XML 技术,它是⼀种可以有效利⽤JavaScript 与 DOM 操作,实现 Web 页⾯局部刷新,⽽不⽤重新加载页⾯的异步通信技术。其核⼼技术是⼀个名为 XMLHttpRequest 的 API,通过JavaScript 的调⽤就可以实现与服务器的通信,以便在已加载成功的页⾯发起请求,再通过 DOM 操作实现页⾯的局部刷新,在早期返回的数据格式是 XML,但是随着更加轻量级的 JSON 出现,现在 Ajax 调⽤多返回 JSON 格式数据,与返回完整 HTML ⽂档不同,局部刷新返回的数据体量更⼩。
Ajax 虽好,但是仍然没有从根本上解决 HTTP 的问题,请求还是得从客户端发起,⽽且客户端也感知不到服务器上资源的更新,如果想要获取某个部分的实时数据,还是得频繁发起 Ajax 请求,造成通信的浪费,只是这个⼯作不⽤⽤户做,可以交给 JavaScript 定时器去做,⽽且基于 Ajax 获取资源也不会刷新页⾯,对⽤户来说,体验上已经好很多。
为了彻底解决实时显⽰服务端资源的问题,必须有⼀种机制能够在服务器资源有更新的时候能够将更新实时推送到客户端,⽽为了实现这种机制,诞⽣了 WebSocket 技术。
WebSocket
WebSocket 本来是作为 HTML5 的⼀部分,⽽现在却变成了⼀个独⽴的协议,它是 Web 客户端与服务器之间实现全双⼯通信的标准。既然是全双⼯,就意味着不是之前那种只能从客户端向服务器发起请求的单向通信,服务端在必要的时候也可以推送信息到客户端,⽽不是被动接收客户端请求再返回响应。
⼀旦客户端与服务器之间建⽴起了基于 WebSocket 协议的通信连接,之后所有的通信都依靠这个协议进⾏,双⽅可以互相发送 JSON、XML、HTML、图⽚等任意格式的数据。由于 WebSocket 是基于 HTTP 协议的,所以连接的发起⽅还是客户端,⽽⼀旦建⽴起 WebSocket 连接,不论是服务器还是客户端,都可以直接向对⽅发送报⽂。
为了实现 WebSocket 的通信,在 HTTP 连接建⽴之后,还需要完成⼀次「握⼿」的步骤:
1)请求阶段
WebSocket 复⽤了 HTTP 的握⼿通道,要建⽴ WebSocket 通信,需要在连接发起⽅的 HTTP 请求报⽂中通过 Upgrade 字段告知服务器通信协议升级到 Websocket,然后通过 Sec-WebSocket-* 扩展字段提供 WebSocket 的协议、版本、键值等信息:
2)响应阶段
对于上述握⼿请求,服务器会返回101 Switching Protocols响应表⽰协议升级成功:
响应头中Sec-WebSocket-Accept字段的值是根据请求头中Sec-WebSocket-Key的字段值⽣成的,两者结合起来⽤于防⽌恶意连接和意外连接。
成功握⼿确⽴ WebSocket 连接后,后续通信就会使⽤ WebSocket 数据帧⽽不是 HTTP 数据帧。下⾯是 WebSocket 通信的时序图:
WebSocket 协议对应的 scheme 是 ws,如果是加密的 WebSocket 对应的 scheme 是 wss,域名、端⼝、路径、参数和 HTTP 协议的 URL ⼀样。
介绍完 WebSocket 的基本原理,给⼤家介绍 WebSocket 的客户端和服务器简单实现,客户端部分基于 JavaScript 的 WebSocket API 即可,服务器将基于 Swoole 实现。
WebSocket 客户端和服务端的简单实现
WebSocket 复⽤了 HTTP 协议来实现握⼿,通过 Upgrade 字段将 HTTP 协议升级到 WebSocket 协议来建⽴ WebSocket 连接,⼀旦 WebSocket 连接建⽴之后,就可以在这个长连接上通过 WebSocket 数据帧进⾏双向通信,客户端和服务端可以在任何时候向对⽅发送报⽂,⽽不是 HTTP 协议那种服务端只有在客户端发起请求后才能响应,从⽽解决了在 Web 页⾯实时显⽰最新资源的问题。
在本篇分享中学院君将在服务端基于 Swoole 实现简单的 WebSocket 服务器,然后在客户端基于 Java
Script 实现 WebSocket 客户端,通过这个简单的实现加深⼤家对 WebSocket 通信过程的理解。
WebSocket 服务器
PHP 异步⽹络通信引擎 Swoole 内置了对 WebSocket 的⽀持,通过⼏⾏ PHP 代码就可以写出⼀个异步⾮阻塞多进程的 WebSocket 服务器:
<?php
// 初始化 WebSocket 服务器,在本地监听 8000 端⼝
$server = new Swoole\WebSocket\Server("localhost", 8000);
// 建⽴连接时触发
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
echo "server: handshake success with fd{$request->fd}\n";
});
// 收到消息时触发推送
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
$server->push($frame->fd, "this is server");
});
// 关闭 WebSocket 连接时触发
$server->on('close', function ($ser, $fd) {
echo "client {$fd} closed\n";
});
// 启动 WebSocket 服务器
$server->start();
将这段 PHP 代码保存到websocket_server.php⽂件。
WebSocket 客户端
在客户端,可以通过 JavaScript 调⽤浏览器内置的 WebSocket API 实现 WebSocket 客户端,实现代码和服务端差不多,⽆论服务端还是客户端WebSocket 都是通过事件驱动的,我们在⼀个 HTML ⽂档中引⼊相应的 JavaScript 代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Chat Client</title>
websocket和socket</head>
<body>
<script>
var nick = prompt("Enter your nickname");
var input = ElementById("input");
input.focus();
// 初始化客户端套接字并建⽴连接
var socket = new WebSocket("ws://localhost:8000");
// 连接建⽴时触发
console.log("Connection open ...");
}
// 接收到服务端推送时执⾏
var msg = event.data;
var node = ateTextNode(msg);
var div = ateElement("div");
div.appendChild(node);
document.body.insertBefore(div, input);
input.scrollIntoView();
};
// 连接关闭时触发
console.log("Connection closed ...");
}
var msg = nick + ": " + input.value;
// 将输⼊框变更信息通过 send ⽅法发送到服务器
socket.send(msg);
input.value = "";
};
}
</script>
<input id="input" >
</body>
</html>
将这个 HTML ⽂档命名为websocket_client.html。在命令⾏启动 WebSocket 服务器:
php websocket.php
然后在浏览器中访问websocket_client.html,⾸先会提⽰我们输⼊昵称:
输⼊之后点击确定,JavaScript 代码会继续往下执⾏,让输⼊框获取焦点,然后初始化 WebSocket 客户端并连接到服务器,这个时候通过开发者⼯具可以看到 Console 标签页已经输出了连接已建⽴⽇志:
这个时候我们在输⼊框中输⼊「你好,WebSocket!」并回车,即可触发客户端发送该数据到服务器,服务器接收到消息后会将其显⽰出来:
同时将「This is server」消息推送给客户端,客户端通过onmessage回调函数将获取到的数据显⽰出来。在开发者⼯具的Network->WS标签页可以查看 WebSocket 通信细节:
看起来,这个过程还是客户端触发服务器执⾏推送操作,但实际上,在建⽴连接并获取到这个客户端的唯⼀标识后,后续服务端资源有更新的情况下,仍然可以通过这个标识主动将更新推送给客户端,⽽不需要客户端发起拉取请求。WebSocket 服务器和客户端在实际项⽬中的实现可能会更加复杂,但是基本原理是⼀致的。
HTTP/2.0 简介
⽬前主流的 HTTP 通信都是基于 HTTP/1.1 的,⽽ HTTP/1.1 ⾃ 1999 年发布的之后再未进⾏过修订,⽽随着互联⽹的蓬勃发展,HTTP/1.1 ⾃⾝所暴露的问题也越来越多,于是负责互联⽹技术标准的 IETF 组织创建了专门的⼯作组来推进下⼀代 HTTP —— HTTP/2.0 的标准化,其⾸要⽬标就是解决 HTTP 的性能瓶颈,缩短 Web 页⾯的加载时间。
HTTP/1.1 的性能瓶颈主要有以下这些:
⼀条连接同时只能发送⼀个请求;
请求只能从客户端发起;
请求/响应⾸部未经压缩就直接发送,⾸部信息越多延迟越⼤;
每次请求/响应都会发送冗长的⾸部信息,造成通信的浪费;
为了解决这些问题,HTTP/2.0 会对 HTTP ⾸部(或者叫做 HTTP 头)进⾏⼀定的压缩,将原来每次通信都要携带的⼤量头信息(键值对)在两端建⽴⼀个索引表,对相同的头只发送索引表中的索引。
另外,HTTP/2.0 协议会将⼀个 TCP 连接切分成多个流,每个流都有⾃⼰的 ID,⽽且流可以是客户端发往服务端,也可以是服务端发往客户端,为了解决并发请求导致响应慢的问题,还可以为流设置优先级。HTTP/2.0 还将所有的传输信息分割为更⼩的消息和帧,并对它们采⽤⼆进制格式编码,常见的帧有 Header 帧,⽤于传输 HTTP 头信息,并且会开启⼀个新的流;再就是 Data 帧,⽤来传输 HTTP 报⽂实体,多个 Data 帧属于同⼀个流。
通过这两种机制,HTTP/2.0 的客户端可以将多个请求分到不同的流中,以实现在⼀个 TCP 连接上处理所有请求,然后将请求内容拆分成帧,进⾏⼆进制传输,这些帧可以打散乱序发送,然后根据每个帧⾸部的流标识符重新组装,并且可以根据优先级,决定优先处理哪个流的数据。
这样⼀来,HTTP/2.0 成功消除了 HTTP/1.1 的性能瓶颈和限制,减少了 TCP 连接数对服务器性能的影响,同时可以将页⾯的多个 css、js、图⽚等资源通过⼀个数据链接进⾏传输,能够加快页⾯组件的传输速度。
HTTP/2.0 虽然⼤⼤增加了并发性,但还是有问题的,为 HTTP/2.0 还是基于 TCP 协议的,TCP 协议在建⽴连接时有额外的开销,在处理包时有严格的顺序要求,当其中⼀个数据包遇到问题,TCP 连接需要等待这个包完成重传之后才能继续进⾏。要解决这些问题只能通过 UDP 协议来实现才可以最⼤化提升 Web 的性能,不过这就属于另⼀个话题了。
HTTP/2.0 标准于 2015 年以正式发表,具体的标准化⼯作由 Chrome、Opera、Firefox、IE、Safari、Edge 等浏览器提供⽀持,⽬前主流浏览器都已经⽀持了该协议。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论