1.http
http是客户端/服务器模式中请求-相应的所用的协议,在这种模式中,客户端(i一般指web浏览器)向服务器提交http请求,服务器响应请求的资源
http的特点
http是半双工协议,也就是说,在同一时刻数据只能单向流动,客户端服务器发送请求(单向的),然后服务器响应(单向的).
服务器不能主动推送数据给浏览器
2.双向通信
Commet是 一种用于web推送的技术,能使服务器实时地将更新的信息送达客户端,而无需客户端发送请求,目前有三种实现方式:
1.轮询(polling)
2.长轮询(long-polling)
3.iframe流(streaming)
2.1轮询
轮询就是客户端和服务器之间会一直进行连接,每隔一段时间就询问有一次
这种方式连接数会很多,一个接受一个发送,每次发送都会有http的header,会很消耗流量,也会消耗CPU的利用率
server.js
let express = require("express");
let app = express();
app.use(express.static(__dirname));
app.use(function(req,res,next){
res.header("Access-Control-Allow-Origin","http://localhost:8000");
})
app.listen(8000);
在浏览器端
<body>
<div id="clock"></div>
<script>
//定时发送http请求
setInterval(function(){
let xhr = new XMLHttpRequest();
xhr.open("GET","http://localhost:8000",true),
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status === 200){
document.querySelector("#clock").innerHTML = xhr.responseText;
}
}
}, 1000)
</script>
</body>
2.2长轮询
长轮询是对轮询的改进版,客户端发送http给服务器之后,看到没有新的消息,就一直等待
当有新消息的时候,才会给客户端,在某种程度上减小了网络带宽以及CUP利用率
由于http数据包的头部数据量往往很大(通常有400kb),但是真正被服务器需要的数据却很少(10kb),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费.
const express = require("express");
const app = express();
//将当前的静态文件托管,如果是static文件
//app.use('static', express.static('public'));
app.use(express.static(__dirname));,
app.get("/clock", (req, res) => {
// console.log("seconds", seconds);
let timer = setInterval(() => {
let date = new Date();
let seconds = date.getSeconds();
if (seconds % 5 == 0) {
res.send(date.toLocaleString());
clearInterval(timer);
}
}, 1000);
})
app.listen(8080);
<div id="clock"></div>
<script>
(function poll() {
let xhr = new XMLHttpRequest();
xhr.open('GET','http://localhost:8080',true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
document.querySelector("#clock").innerHTML = xhr.responseText;
// 服务器的数据回来之后,再发下一次请求
poll()
}
}
xhr.send();
})();
</script>
1.3 iframe 流
通过HTML页面嵌入一个隐藏的iframe,然后将这个iframe的src属性设为一个长连接的请求,服务器就能源源不断的往客户端推送数据
const express = require("express");
const app = express();
app.use(express.static(__dirname));
app.get("/clock", function (req, res) {
console.log("fangwenle")
//定时给浏览器发送数据
setInterval(function () {
res.write(`
<script type="text/javascript">
parent.document.getElementById('clock').innerHTML = "${new Date().toLocaleString()}"
</script>
`)
}, 1000)
})
app.listen(8000);
浏览器端
<div id="clock"></div>
<iframe src="/clock" style=" display:none" />
1.4 EventSource流
HTML5规范中提供了服务端事件EventSource,浏览器在实现了该规范的前提下创建了衣蛾EventSource连接后,便可以收到服务端发送的消息,这些消息需要遵循一定的格式,对于前端开发人员而言,只需在浏览器中侦听对应的事件即可
SEE 的简单模型是:一个客户端去服务器端订阅一条
流
,之后服务器可以发送消息给客户端直到服务器客户端关闭该流
,所以eventsource叫做server-sent-event;
EventSource 流的实现方式对于客户端开发人员而言非常简单,兼容性良好
对于服务端,它可以兼容老的浏览器,无需upgrade为其他的协议, 在简单的服务端推送的场景下可以满足需求:
html页面
var eventSource = new EventSource("/eventoSource");
// "/eventSource" 为接口地址
// 监听服务器端发过来的事件
eventSource.onmessage = function(event){
let message = event.data
clock.innerHTML = message;
console.log(event.data);
}
// 监听链接请求失败的错误事件
eventSource.onerror = function(err){
console.log(err);
}
服务端
const express = require("express");
const app = express();
app.use(express.static(__dirname));
app.get("/eventSource", (req, res) => {
res.header("Content-Type", "text/event-stream");
setInterval(() => {
res.write(`event:message\ndata:${new Date().toLocaleString()}\n\n`);
}, 1000);
res.on("close", function () {
clearInterval($timer);
})
})
app.listen(8000);
1.4.2 服务器
事件流的对应的MIME格式为它text/event-stream,而且其基于HTTP长连接,针对HTTP1.1规范默认为采用长连接,针对http1.0的服务器需要特殊设置
event-source 必须编码成utf-8的格式,并且需要下面4个规范定义好的字段:
Event 事件类型
Data 发送的数据
ID:每条事件流的ID
Retry :告知浏览器在所有的连接丢失以后重新开启新的连接的过程,之前收到的最后事件流ID被发送到服务器
在服务器端
const express = require("express");
const app = express();
app.use(express.static(__dirname));
app.get("/EventSource", (req, res) => {
res.header("Content-Type", "text/event-stream");
let $timer = setInterval(() => {
res.write(`event:message\ndata:${new Date().toLocaleString()}\n\n`);
}, 1000);
res.on("close", function () {
clearInterval($timer);
})
})
app.listen(8000);
在服务器端如果数据量较大
const express = require("express");
const app = express();
app.use(express.static(__dirname));
const SseStream = require("ssestream");
let sendCount = 1;
app.get("/EventSource", (req, res) => {
const sseStream = new SseStream(req);
sseStream.pipe(res);
const pusher = setInterval(() => {
sseStream.write({
id: sendCount++,
event: "message",
retry: 20000,
data: { ts: new Date().toTimeString() }
})
}, 1000);
res.on("close", () => {
clearInterval(pusher);
sseStream.unpipe(res);
})
})
app.listen(8888)
在浏览器端
<div id="clock"></div>
<script>
let eventSource = new EventSource("/EventSource");
let clock = document.querySelector("#clock");
eventSource.onmessage = function (evnet) {
let message = event.data;
clock.innerHTML = message;
}
eventSource.onerror = function (event) {
console.log("event.error", event.error)
}
</script>
2.websocket
websocket_api规范顶一个了一个API用于在网页与浏览器和服务器建立一个socket连接, 在客户端和服务器保持连接,两边可以在任意时间开始发送数据
HTML5开始提供的一种浏览器与服务器进行双工通讯的网络技术
属于应用层协议,它基于TCP传输协议,并服用了HTTP的握手通道
2.1 websocket优势
支持双向通信
更好的二进制支持
较少的控制开销, 连接创建后,ws客服端、服务端、进行数据交换、协议控制和数据包头部较小
2.2 websocket 实战
服务器端
const express = require("express");
const app = express();
app.use(express.static(__dirname));
app.get('/', (req, res) => {
res.header("Content-Type", "text/event-stream");
res.sendFile(path.resolve(__dirname, "index.html"));
})
app.listen(3000);
------------------------
let WebSocketServer = require("ws").Server;
let wsServer = new WebSocketServer({ port: 8888 });
wsServer.on("connection", function (socket) {
console.log("连接成功");
socket.on("message", message => {
console.log('接收到客户端消息:' + message);
socket.send("服务器回应:" + message);
})
})
浏览器端
// ws创建的连接允许跨域
let ws = new WebSocket("ws://localhost:8888");
ws.onopen = function () {
ws.send('hello');
}
ws.onmessage = function (event) {
console.log(event.data);
}
2.3如何建立连接
- websocket 复用了HTTP的握手通道,具体指,客户端通过HTTP请求与websocket服务器协商升级协议,协议升级完成后,后台的数据交换
- 遵照websocket的协议
客户端:申请协议升级
- 首先,客户端发起协议升级的请求, 采用HTTP报文的格式,且只支持GET方法
GET ws://localhost:8888/ HTTP/1.1
Host: localhost:8888
Connection: Upgrade
Upgrade: websocket //表示升级到websocket协议
Sec-WebSocket-Version: 13 //表示websocket的版本
Sec-WebSocket-Key: IHfMdf8a0aQXbwQO1pkGdA==
//与后台服务端相应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意链接,无意链接