[go]websocket

web实时更新技术

  • http的特点
  1. 半双工: 同一个时刻,只能单向传数据(request/response).
  2. 服务器不能主动给客户端推消息

  • 轮询(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的格式
消息的每个字段使用"
"来做分割,最后用

表示结束
常用的消息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++}
event:message
data:${new Date().toLocaleTimeString()}

`);
    }, 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))
}

WebSocket 教程: 浏览器api

  • 最简单的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>
原文地址:https://www.cnblogs.com/iiiiiher/p/12209115.html