• 与 Web 服务器通信


    • XMLHttpRequest:能够从客户端发送请求到服务器端并且得到响应。一个小demo在这里
      但XHR不适合快速来回发送消息(如聊天室),并且无法将这一次调用和下一次调用联系起来(每次发起请求,服务器都要确定请求来自何方)。

    • 轮询:浏览器定期向服务器发送消息并接受服务器回答是否有数据更新。(考虑轮询间隔时间过长/过短的问题)

    • COMET(长轮询):在每次请求的时候,服务器端保持该连接在一段时间内处于打开状态,而不是在响应完成之后立即关闭。
      在打开状态的这一段时间里,有数据更新可以立即得到响应。
      上一个长连接关闭之后,浏览器立即打开一个新的长连接继续请求。

    • COMET(流):浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性向浏览器发送数据。

    • Server-Sent events:适合服务器端向客户端不断的数据推送,更容易实现了comet。但整个通信完全单向,无法知道浏览器是否响应。

    • WebSocket:浏览器能够保持对Web服务器打开的连接,从而与服务器长时间交换数据。适合聊天室、大型多人游戏、端到端写作工具等。

    WebSocket

    WebSocket 是 HTML5 一种新的协议。实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据。URL 模式变为了 ws:// 或 wss://。

    在 JavaScript 创建了WebSocket之后,会有一个 HTTP 请求发起连接。在取得服务器响应后,建立的连接会从 HTTP 协议交换为 WebSocket 协议。也就是说,需要支持这种协议的专门服务器才能工作。

    WebSocket API 走马观花

    • 创建一个实例对象,创建后浏览器马上尝试连接:
    // 应该传入绝对url,ws://www.example.com/server
    var socket = new WebSocket(url);
    
    • 表示当前状态的 readyState 属性,其值永远从0开始:
      WebSocket.OPENING(0) 正在建立连接
      WebSocket.OPEN(1) 已经建立连接
      WebSocket.CLOSING(2) 正在关闭连接
      WebSocket.CLOSE(3) 已经关闭连接

    • 事件
      open:成功建立连接
      send:向服务器发送数据,只能发送纯文本数据,要传JSON数据需要先序列化
      message:接收到服务器传来数据,数存据在evt.data,同样接收的数据也是字符串,需要手工解析
      error:发生错误
      close:连接关闭

    WebSocket 机制

    WebSocket 实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:

    • WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;

    • WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。

    一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 服务器端断掉连接前,不需要客户端和服务端重新发起连接请求。

    交互的报文

    • WebSocket 客户端请求报文:
    GET ws://localhost:3000/ HTTP/1.1
    Host: localhost:3000
    Connection: Upgrade
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade: websocket
    Origin: file://
    Sec-WebSocket-Version: 13
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
    Accept-Encoding: gzip, deflate, sdch
    Accept-Language: zh-CN,zh;q=0.8
    Sec-WebSocket-Key: 23li+QvHohBue1I3rm2VmA==
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    

    可见,发起请求的是传统 HTTP 协议。“Upgrade:websocket” 表明这是 WebSocket 类型请求。
    “Sec-WebSocket-Key” 是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的 “Sec-WebSocket-Accept” 应答,否则客户端会抛出 “Error during WebSocket handshake” 错误,并关闭连接。

    • WebSocket 服务端响应报文:
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: dpuNuwoCREWFf0z2sQ1F2u2Sb50=
    Origin: file://
    

    “Sec-WebSocket-Accept” 的值是服务端采用与客户端一致的密钥计算出来后返回客户端的。
    “HTTP/1.1 101 Switching Protocols” 表示服务端接受 WebSocket 协议的客户端连接,握手成功,后续就可以进行 TCP 通讯了。

    WebSocket 实现

    • 客户端:
    <div id="output"></div>
    <script>
        function checkBrowser() {
          if (!window.WebSocket) {
            console.log("Not support WebSocket!");
          }
        }
          
        function openWS(e) {
          log("ws opened.", e);
          sendMessage("Conan");
        };
    
        function closeWS(e) {
          log("ws closed", e);
          log("was clean?" + e.wasClean + " code=" + e.code + " reason=" + e.reason);
        };
    
        function receiveMsg(e) {
          log("RECEIVED: " + e.data, e);
          // ws.close();
        };
    
        function errorOccured(e) {
          log('Error occured: ' + e.data, e);
        };
    
        function sendMessage(msg) {
          ws.send(msg);
          log("SEND : "+ msg);
        };
    
        function log(s, e) {
          var output = document.getElementById("output");
          var p = document.createElement("p");
    
          p.style.wordWrap = "break-word";
          p.style.padding = "10px";
          p.style.background = "#eee";
          p.textContent = "LOG : " + s;
          output.appendChild(p);
        }
    
        var ws, output;
        window.onload = function() {
          output = document.getElementById("output");
    
          ws = new WebSocket("ws://localhost:3000");
          ws.onopen = openWS;
          ws.onmessage = receiveMsg;
          ws.onclose = closeWS;
          ws.onerror = errorOccured;
        };
    </script>
    
    • 服务器端:
      已经开源的库可以直接使用,如node-WebSocket,socket.io等。
    process.title = 'node-chat';
    
    // Port where we'll run the websocket server
    var webSocketsServerPort = 3000;
    
    // websocket and http servers
    var webSocketServer = require('websocket').server;
    var http = require('http');
    
    // latest 100 messages
    var history = [ ];
    // list of currently connected clients (users)
    var clients = [ ];
    
    /**
     * Helper function for escaping input strings
     */
    function htmlEntities(str) {
        return String(str).replace(/&/g, '&').replace(/>/g, '>').replace(/"/g, '"');
    }
    
    // Array with some colors
    var colors = [ 'red', 'green', 'blue', 'magenta', 'purple', 'plum', 'orange' ];
    // ... in random order
    colors.sort(function(a,b) { return Math.random() > 0.5; } );
    
    /**
     * HTTP server
     */
    var server = http.createServer(function(request, response) {
        // Not important for us. We're writing WebSocket server, not HTTP server
    }).listen(webSocketsServerPort);
    
    /**
     * WebSocket server
     */
    var wsServer = new webSocketServer({
        // WebSocket server is tied to a HTTP server. WebSocket request is just
        // an enhanced HTTP request. For more info http://tools.ietf.org/html/rfc6455#page-6
        httpServer: server
    });
    
    // This callback function is called every time someone tries to connect to the WebSocket server
    wsServer.on('request', function(request) {
        console.log((new Date()) + ' Connection from origin ' + request.origin + '.');
    
        // accept connection - you should check 'request.origin' to make sure that
        // client is connecting from your website
        // (http://en.wikipedia.org/wiki/Same_origin_policy)
        var connection = request.accept(null, request.origin); 
        // we need to know client index to remove them on 'close' event
        var index = clients.push(connection) - 1;
        var userName = false;
        var userColor = false;
    
        console.log((new Date()) + ' Connection accepted.');
    
        // send back chat history
        if (history.length > 0) {
            connection.sendUTF(JSON.stringify( { type: 'history', data: history} ));
        }
    
        // user sent some message
        connection.on('message', function(message) {
            if (message.type === 'utf8') { // accept only text
                if (userName === false) { // first message sent by user is their name
                    // remember user name
                    userName = htmlEntities(message.utf8Data);
                    // get random color and send it back to the user
                    userColor = colors.shift();
                    connection.sendUTF(JSON.stringify({ type:'color', data: userColor }));
                    console.log((new Date()) + ' User is known as: ' + userName
                                + ' with ' + userColor + ' color.');
    
                } else { // log and broadcast the message
                    console.log((new Date()) + ' Received Message from '
                                + userName + ': ' + message.utf8Data);
    
                    // we want to keep history of all sent messages
                    var obj = {
                        time: (new Date()).getTime(),
                        text: htmlEntities(message.utf8Data),
                        author: userName,
                        color: userColor
                    };
                    history.push(obj);
                    history = history.slice(-100);
    
                    // broadcast message to all connected clients
                    var json = JSON.stringify({ type:'message', data: obj });
                    for (var i=0; i < clients.length; i++) {
                        clients[i].sendUTF(json);
                    }
                }
            }
        });
    
        // user disconnected
        connection.on('close', function(connection) {
            if (userName !== false && userColor !== false) {
                console.log((new Date()) + " Peer "
                    + connection.remoteAddress + " disconnected.");
                // remove user from the list of connected clients
                clients.splice(index, 1);
                // push back user's color to be reused by another user
                colors.push(userColor);
            }
        });
    
    });
    

    参考

    WebSocket实战

  • 相关阅读:
    46 Simple Python Exercises-Higher order functions and list comprehensions
    IDEA一些设置
    DDD建模案例----“视频课程”场景
    LA 4727
    uva 1377
    uva 1421
    UVA
    LA 4731
    uva 11404
    uva 11143
  • 原文地址:https://www.cnblogs.com/travelling-wxy/p/4992706.html
Copyright © 2020-2023  润新知