• 【计算机网络】 7、websocket 概念、sdk、实现


    在这里插入图片描述

    一、背景

    已经有了 http 协议,为什么还需要 websocket 协议呢?是因为 http 只是单向的,client 向 server 获取消息。但若 server 本身有连续状态变化时,client 很难感知,此时就有如下两种解决方案:

    • client 轮训:很浪费资源(需不断打开+关闭连接),不及时
    • websocket 协议

    二、简介

    websocket 协议在 2008 年产生,2011 年成为国际标准,所有浏览器都支持了。是 server push 技术 之一

    在这里插入图片描述
    在这里插入图片描述

    其特点包括:

    • 建立在 TCP 协议之上,服务器端的实现比较容易。
      在这里插入图片描述

    • 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

      • WebSocket协议是独立的基于TCP的协议。他和HTTP的唯一关系是建立连接的握手操作的升级请求是基于HTTP服务器的。
      • WebSocket默认使用80端口进行连接,而基于TLS(RFC2818)的WebSocket连接是基于443端口的。
    • 数据格式比较轻量,性能开销小,通信高效。

    • 可以发送文本,也可以发送二进制数据。

    • 没有同源限制,客户端可以与任意服务器通信。

    • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。ws://example.com:80/some/path

    三、client

    可在 这里 在线运行如下client 示例:

    var ws = new WebSocket("wss://echo.websocket.org");
    
    ws.onopen = function(evt) { 
      console.log("Connection open ..."); 
      ws.send("Hello WebSockets!");
    };
    
    ws.onmessage = function(evt) {
      console.log( "Received Message: " + evt.data);
      ws.close();
    };
    
    ws.onclose = function(evt) {
      console.log("Connection closed.");
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.1 ws 构造函数

    执行如下即会使 client 就会与 server 进行连接,ws 对象的 API 详见

    var ws = new WebSocket('ws://localhost:8080');
    
    • 1

    3.2 ws.readyState

    返回实例对象的当前状态,共有四种。

    • CONNECTING:值为0,表示正在连接。
    • OPEN:值为1,表示连接成功,可以通信了。
    • CLOSING:值为2,表示连接正在关闭。
    • CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

    下面是一个示例:

    switch (ws.readyState) {
      case WebSocket.CONNECTING:
        // do something
        break;
      case WebSocket.OPEN:
        // do something
        break;
      case WebSocket.CLOSING:
        // do something
        break;
      case WebSocket.CLOSED:
        // do something
        break;
      default:
        // this never happens
        break;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.3 ws.onopen

    指定连接成功后的回调函数:

    ws.onopen = function () {
      ws.send('Hello Server!');
    }
    
    • 1
    • 2
    • 3

    如果要指定多个回调函数,可以使用 addEventListener():

    ws.addEventListener('open', function (event) {
      ws.send('Hello Server, i am callback func1!');
    });
    ws.addEventListener('open', function (event) {
      ws.send('Hello Server, i am callback func 2!');
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.4 ws.onclose

    指定连接关闭后的回调函数:

    ws.onclose = function(event) {
      var code = event.code;
      var reason = event.reason;
      var wasClean = event.wasClean;
      // handle close event
    };
    
    ws.addEventListener("close", function(event) {
      var code = event.code;
      var reason = event.reason;
      var wasClean = event.wasClean;
      // handle close event
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.5 ws.onmessage

    ws.onmessage = function(event) {
      var data = event.data;
      // 处理数据
    };
    ws.addEventListener("message", function(event) {
      var data = event.data;
      // 处理数据
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意,服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。

    ws.onmessage = function(event){
      if(typeof event.data === String) {
        console.log("Received data string");
      }
    
      if(event.data instanceof ArrayBuffer){
        var buffer = event.data;
        console.log("Received arraybuffer");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.6 ws.send

    向 server 发数据

    // 发文本
    ws.send('your message');
    
    // 发 Blob 对象
    var file = document
      .querySelector('input[type="file"]')
      .files[0];
    ws.send(file);
    
    // 发 ArrayBuffer 对象
    // Sending canvas ImageData as ArrayBuffer
    var img = canvas_context.getImageData(0, 0, 400, 320);
    var binary = new Uint8Array(img.data.length);
    for (var i = 0; i < img.data.length; i++) {
      binary[i] = img.data[i];
    }
    ws.send(binary.buffer);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.7 ws.bufferedAmount

    表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

    var data = new ArrayBuffer(10000000);
    socket.send(data);
    
    if (socket.bufferedAmount === 0) {
      // 发送完毕
    } else {
      // 发送还没结束
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.8 ws.onerror

    报错时的回调函数

    socket.onerror = function(event) {
      // handle error event
    };
    socket.addEventListener("error", function(event) {
      // handle error event
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    四、server

    4.1 go

    gorilla/websocket 库文档

    package main
    
    import (
    	"fmt"
    	"github.com/gorilla/websocket"
    	"log"
    	"net/http"
    )
    
    func main() {
    	fmt.Println("Hello World")
    	setupRoutes()
    	log.Fatal(http.ListenAndServe(":8080", nil))
    }
    
    func setupRoutes() {
    	http.HandleFunc("/", homePage)
    	http.HandleFunc("/ws", wsEndpoint)
    }
    
    func homePage(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprintf(w, "Home Page")
    }
    
    func wsEndpoint(w http.ResponseWriter, r *http.Request) {
    	// We'll need to define an Upgrader, this will require a Read and Write buffer size
    	var upgrader = websocket.Upgrader{
    		ReadBufferSize:  1024,
    		WriteBufferSize: 1024,
    	}
    
    	upgrader.CheckOrigin = func(r *http.Request) bool { return true } // 允许跨域
    
    	// upgrade this connection to a WebSocket connection
    	ws, err := upgrader.Upgrade(w, r, nil)
    	if err != nil {
    		log.Println(err)
    	}
    
    	log.Println("Client Connected")
    	err = ws.WriteMessage(1, []byte("Hi Client!"))
    	if err != nil {
    		log.Println(err)
    	}
    
    	reader(ws)
    }
    
    // listen indefinitely for new messages coming through on our WebSocket connection
    func reader(conn *websocket.Conn) {
    	for {
    		// read in a message
    		messageType, p, err := conn.ReadMessage()
    		if err != nil {
    			log.Println(err)
    			return
    		}
    		// print out that message for clarity
    		fmt.Println(string(p))
    
    		if err := conn.WriteMessage(messageType, p); err != nil {
    			log.Println(err)
    			return
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    4.1.1 apifox client

    首先建立连接
    在这里插入图片描述

    然后发送数据,也可同时接收数据:

    在这里插入图片描述

    实际请求如下:
    在这里插入图片描述

    server 端打印日志如下:
    在这里插入图片描述

    4.1.2 js client

    DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Go WebSocket Tutorialtitle>
      head>
      <body>
        <h2>Hello Worldh2>
    
        <script>
            let socket = new WebSocket("ws://127.0.0.1:8080/ws");
            console.log("Attempting Connection...");
    
            socket.onopen = () => {
                console.log("Successfully Connected");
                socket.send("Hi From the Client!")
            };
            
            socket.onclose = event => {
                console.log("Socket Closed Connection: ", event);
                socket.send("Client Closed!")
            };
    
            socket.onerror = error => {
                console.log("Socket Error: ", error);
            };
    
        script>
      body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在 F12 可看到 日志和 ws 收发记录:

    在这里插入图片描述

    在这里插入图片描述

    五、nginx配置

    		location /mywsapp/ {
                        proxy_http_version 1.1;
                        proxy_set_header Upgrade $http_upgrade;
                        proxy_set_header Connection "Upgrade";
                        proxy_set_header Proxy "";
                        proxy_set_header Host $http_host;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                        proxy_set_header X-Forwarded-Proto $scheme;
                        proxy_pass http://127.0.0.1:10002/;
                    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    则会把前端的 ws://192.168.2.99/mywsapp/api/v2/websocket 转发为 ws://192.168.2.99:10002/api/v2/websocket,并添加各种 ws 的 header

    ws go 教程
    websocket 介绍
    ws 协议
    rfc ws 协议中文翻译

  • 相关阅读:
    .net 项目使用 JSON Schema
    猿创征文|SfM(Structure from Motion)学习之路
    Debug Interface Access(DIA)(一)
    中秋快乐! Happy Mid-autumn Festival!
    Shiro-官方文档及使用
    【算法-字符串1】反转字符串 + 反转字符串2
    逆天改命,专科学历,五面京东成功斩获Offer
    无源蜂鸣器驱动实验
    js箭头函数
    redux一步一步的使用
  • 原文地址:https://blog.csdn.net/jiaoyangwm/article/details/130760176