服务器与客户端的通信之websocket

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是配套的,提供基本的防护,比如恶意链接,无意链接
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值