web实时更新技术
- http的特点
- 半双工: 同一个时刻,只能单向传数据(request/response).
- 服务器不能主动给客户端推消息
- 轮询(polling)
不断的建立http连接,严重浪费了服务器端和客户端的资源. 人越多,服务器压力越大.
- server.js
let express = require(\'express\');
let app = express();
app.use(express.static(__dirname));
app.get("/clock", function (req, res) {
res.end(new Date().toLocaleTimeString());
});
app.listen(8080);
- client
<body>
<div id="clock"></div>
<script>
setInterval(function () {
let xhr = new XMLHttpRequest();
xhr.open(\'GET\', \'http://localhost:8080/clock\', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
document.querySelector(\'#clock\').innerhtml = xhr.responseText;
}
};
xhr.send();
}, 1000);
</script>
</body>
- 访问http://localhost:8080/clock
- 长轮询(long polling)(comet)
当一次请求完成后, 在发起进行下一次
- client
<body>
<div id="clock"></div>
<script>
setInterval(function () {
let xhr = new XMLHttpRequest();
xhr.open(\'GET\', \'http://localhost:8080/clock\', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
document.querySelector(\'#clock\').innerHTML = xhr.responseText;
}
};
xhr.send();
}, 1000);
</script>
</body>
- server.js
let express = require(\'express\');
let app = express();
app.use(express.static(__dirname));
app.get("/clock", function (req, res) {
//优化: 当时间为每隔5的倍数才返回.
let $timer = setInterval(function () {
let date = new Date();
let seconds = date.getSeconds();
if (seconds % 5 === 0) {
res.end(new Date().toLocaleTimeString());
clearInterval($timer)
}
}, 1000);
});
app.listen(8080);
- iframe流
打开浏览器会主动请求iframe页
iframe可以调用parent父类的方法
缺点: server不断开连接.浏览器一直转圈.
- server.js
const express = require(\'express\');
const app = express();
app.use(express.static(__dirname));
app.get(\'/clock\', function (req, res) {
res.header("Content-Type", "text/html");
setInterval(function () {
res.write(`
<script type="text/javascript">
parent.setTime("${new Date().toLocaleTimeString()}");
</script>
`);
}, 1000);
});
app.listen(8080);
- client
<body>
<div id="clock"></div>
<iframe src="/clock" style="display:none"></iframe>
<script>
function setTime(ts) {
document.querySelector(\'#clock\').innerHTML = ts;
}
</script>
</body>
- 长连接(SSE)
SSE的简单模型是:
一个客户端去从服务器端订阅一条流,
之后服务端可以发送消息给客户端直到服务端或者客户端关闭该“流”,
所以eventsource也叫作"server-sent-event`
MIME格式为text/event-stream
必须编码成utf-8的格式
消息的每个字段使用"\\n"来做分割,最后用\\n\\n表示结束
常用的消息key
Event: 事件类型,消息名称要和前端对应,如定义的event: xxx, 则前端可以用onxxx对应
Data: 发送的数据
ID: 每一条事件流的ID
不支持跨域
- server
let express = require(\'express\');
let app = express();
app.use(express.static(__dirname));
let sendCount = 1;
app.get(\'/eventSource\', function (req, res) {
res.header(\'Content-Type\', \'text/event-stream\',);
setInterval(() => {
res.write(`id:${sendCount++}\\nevent:message\\ndata:${new Date().toLocaleTimeString()}\\n\\n`);
}, 1000)
});
app.listen(8080);
- client
<body>
<div id="clock"></div>
<script>
var eventSource = new EventSource(\'/eventSource\');
eventSource.onmessage = function (e) {
document.querySelector(\'#clock\').innerHTML =e.data
};
eventSource.onerror = function (err) {
console.log(err);
}
</script>
</body>
- WebSocket
- server.js
const path = require(\'path\');
let app = express();
let server = require(\'http\').createServer(app);
app.get(\'/\', function (req, res) {
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\', function (message) {
console.log(\'接收到客户端消息:\' + message);
socket.send(\'服务器回应:\' + message);
});
});
- client
<script>
let ws = new WebSocket(\'ws://localhost:8888\');
ws.onopen = function () {
console.log(\'客户端连接成功\');
ws.send(\'hello\');
}
ws.onmessage = function (event) {
console.log(\'收到服务器的响应 \' + event.data);
}
</script>
go websocket
- server.go: gorilla/websocket
package main
import (
"github.com/gorilla/websocket"
"log"
"net/http"
)
var upgrader = websocket.Upgrader{
// 解决跨域问题
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func main() {
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
//升级w, 此后用conn读写消息
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
for {
messageType, p, err := conn.ReadMessage() //会读阻塞
if err != nil {
log.Println(err)
return
}
if err := conn.WriteMessage(messageType, p); err != nil {
log.Println(err)
return
}
}
})
log.Fatal(http.ListenAndServe(":3000", nil))
}
- 最简单的ws的客户端
<script>
let ws = new WebSocket("wss://echo.websocket.org");
//打开ws连接
ws.onopen = function (event) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
//收到消息时回调
ws.onmessage = function (event) {
console.log("Received Message: " + event.data);
ws.close();
};
//关闭连接时回调(直接x掉浏览器,服务端也会感知)
ws.onclose = function (event) {
console.log("Connection closed.");
};
</script>
- 支持手动连接,反复收发的客户端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<table>
<tr>
<td valign="top" width="50%">
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<br>
<input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td>
<td valign="top" width="50%">
<div id="output"></div>
</td>
</tr>
</table>
<script>
window.addEventListener("load", function (event) {
let output = document.getElementById("output");
let input = document.getElementById("input");
let ws;
let print = function (message) {
let d = document.createElement("div");
d.innerHTML = message;
output.appendChild(d);
};
document.getElementById("open").onclick = function (event) {
if (ws) return false;
ws = new WebSocket("ws://localhost:3000/ws");
ws.onopen = function (event) {
print("OPEN");
};
ws.onclose = function (event) {
print("CLOSE");
ws = null;
};
ws.onmessage = function (event) {
print("RESPONSE: " + event.data);
};
ws.onerror = function (event) {
print("ERROR: " + event.data);
};
return false;
};
document.getElementById("send").onclick = function (event) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function (event) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</body>
</html>