zoukankan      html  css  js  c++  java
  • WebSocket 学习(三)--用nodejs搭建服务器

    前面已经学习了WebSocket API,包括事件、方法和属性。详情:WebSocket(二)--API  WebSocket是基于事件驱动,支持全双工通信。下面通过三个简单例子体验一下。

    简单开始

    1.安装node。https://nodejs.org/en/  

    2.安装ws模块

    ws:是nodejs的一个WebSocket库,可以用来创建服务。 https://github.com/websockets/ws

    3.server.js

    在项目里面新建一个server.js,创建服务,指定8181端口,将收到的消息log出来。

    var WebSocketServer = require('ws').Server,
    wss = new WebSocketServer({ port: 8181 });
    wss.on('connection', function (ws) {
        console.log('client connected');
        ws.on('message', function (message) {
            console.log(message);
        });
    });

    4.建立一个client.html。

    在页面上建立一个WebSocket的连接。用send方法发送消息。

     var ws = new WebSocket("ws://localhost:8181");
        ws.onopen = function (e) {
            console.log('Connection to server opened');
        }
        function sendMessage() {
            ws.send($('#message').val());
        }

    页面:

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>WebSocket Echo Demo</title>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <link href="../bootstrap-3.3.5/css/bootstrap.min.css" rel="stylesheet" />
        <script src="../js/jquery-1.12.3.min.js"></script>
        <script src="../js/jquery-1.12.3.min.js"></script>
        <script src="../bootstrap-3.3.5/js/bootstrap.min.js"></script>
        <script>
        var ws = new WebSocket("ws://localhost:8181");
        ws.onopen = function (e) {
            console.log('Connection to server opened');
        }
        function sendMessage() {
            ws.send($('#message').val());
        }
        </script>
    </head>
    
    <body >
        <div class="vertical-center">
            <div class="container">
                <p>&nbsp;</p>
                <form role="form" id="chat_form" onsubmit="sendMessage(); return false;">
                    <div class="form-group">
                        <input class="form-control" type="text" name="message" id="message"
                               placeholder="Type text to echo in here" value="" />
                    </div>
                    <button type="button" id="send" class="btn btn-primary"
                            onclick="sendMessage();">
                        Send!
                    </button>
                </form>
            </div>
        </div>
    </body>
    </html>
    View Code

    运行之后如下,服务端即时获得客户端的消息。

    模拟股票

    上面的例子很简单,只是为了演示如何运用nodejs的ws创建一个WebSocket服务器。且可以接受客户端的消息。那么下面这个例子演示股票的实时更新。客服端只需要连接一次,服务器端会不断地发送新数据,客户端收数据后更新UI.页面如下,有五只股票,开始和停止按钮测试连接和关闭。

     服务端:

     1.模拟五只股票的涨跌。

    var stocks = {
        "AAPL": 95.0,
        "MSFT": 50.0,
        "AMZN": 300.0,
        "GOOG": 550.0,
        "YHOO": 35.0
    }
    function randomInterval(min, max) {
        return Math.floor(Math.random() * (max - min + 1) + min);
    }
    var stockUpdater;
    var randomStockUpdater = function() {
        for (var symbol in stocks) {
            if(stocks.hasOwnProperty(symbol)) {
                var randomizedChange = randomInterval(-150, 150);
                var floatChange = randomizedChange / 100;
                stocks[symbol] += floatChange;
            }
        }
        var randomMSTime = randomInterval(500, 2500);
        stockUpdater = setTimeout(function() {
            randomStockUpdater();
        }, randomMSTime);
    }
    randomStockUpdater();

    2.连接建立之后就开始更新数据

    wss.on('connection', function (ws) {
        var sendStockUpdates = function (ws) {
            if (ws.readyState == 1) {
                var stocksObj = {};
                for (var i = 0; i < clientStocks.length; i++) {
                  var symbol = clientStocks[i];
                    stocksObj[symbol] = stocks[symbol];
                }
                if (stocksObj.length !== 0) {
                    ws.send(JSON.stringify(stocksObj));//需要将对象转成字符串。WebSocket只支持文本和二进制数据
                    console.log("更新", JSON.stringify(stocksObj));
                }
               
            }
        }
        var clientStockUpdater = setInterval(function () {
            sendStockUpdates(ws);
        }, 1000);
        ws.on('message', function (message) {
            var stockRequest = JSON.parse(message);//根据请求过来的数据来更新。
            console.log("收到消息", stockRequest);
            clientStocks = stockRequest['stocks'];
            sendStockUpdates(ws);
        });

    客户端:

    建立连接:

     var ws = new WebSocket("ws://localhost:8181");

    onopen直接只有在连接成功后才会触发,在这个时候将客户端需要请求的股票发送给服务端。

    var isClose = false;
        var stocks = {
            "AAPL": 0, "MSFT": 0, "AMZN": 0, "GOOG": 0, "YHOO": 0
        };
        function updataUI() {
            ws.onopen = function (e) {
                console.log('Connection to server opened');
                isClose = false;
                ws.send(JSON.stringify(stock_request));
                console.log("sened a mesg");
            }
            //更新UI
            var changeStockEntry = function (symbol, originalValue, newValue) {
                var valElem = $('#' + symbol + ' span');
                valElem.html(newValue.toFixed(2));
                if (newValue < originalValue) {
                    valElem.addClass('label-danger');
                    valElem.removeClass('label-success');
                } else if (newValue > originalValue) {
                    valElem.addClass('label-success');
                    valElem.removeClass('label-danger');
                }
            }
            // 处理受到的消息
            ws.onmessage = function (e) {
                var stocksData = JSON.parse(e.data);
                console.log(stocksData);
                for (var symbol in stocksData) {
                    if (stocksData.hasOwnProperty(symbol)) {
                        changeStockEntry(symbol, stocks[symbol], stocksData[symbol]);
                        stocks[symbol] = stocksData[symbol];
                    }
                }
            };
        }
    
        updataUI();

    运行效果如下:只需要请求一次,数据就会不断的更新,效果是不是很赞,不用轮询,也不用长连接那么麻烦了。文章末尾会附上所有源码。

    (美股的涨跌和A股的颜色是反的,即红跌绿涨)

    实时聊天

    上面的例子是连接建立之后,服务端不断给客户端发送数据。接下来例子是一个简单的聊天室类的例子。可以建立多个连接。

    1.安装node-uuid模块,用来给每个连接一个唯一号。

    2.服务端消息发送

    消息类型分notification和message两种,前者是提示信息,后者是聊天内容。消息还包含一个id、昵称和消息内容。在上一节有学习到readyState有四个值,OPEN表示连接建立可以发送消息。如果页面关闭了,为WebSocket.CLOSE。

    function wsSend(type, client_uuid, nickname, message) {
        for (var i = 0; i < clients.length; i++) {
            var clientSocket = clients[i].ws;
            if (clientSocket.readyState === WebSocket.OPEN) {
                clientSocket.send(JSON.stringify({
                    "type": type,
                    "id": client_uuid,
                    "nickname": nickname,
                    "message": message
                }));
            }
        }
    }

    3.服务端处理连接

    每新增加一个连接,都会发送一条匿名用户的加入的提示消息,如果消息中带有“/nick” 认为这一个修改昵称的消息。然后更新客户端的昵称。其他都会当做聊天消息处理。

    wss.on('connection', function(ws) {
        var client_uuid = uuid.v4();
        var nickname = "AnonymousUser" + clientIndex;
        clientIndex += 1;
        clients.push({ "id": client_uuid, "ws": ws, "nickname": nickname });
        console.log('client [%s] connected', client_uuid);
        var connect_message = nickname + " has connected";
        wsSend("notification", client_uuid, nickname, connect_message);
        console.log('client [%s] connected', client_uuid);
        ws.on('message', function(message) {
            if (message.indexOf('/nick') === 0) {
                var nickname_array = message.split(' ');
                if (nickname_array.length >= 2) {
                    var old_nickname = nickname;
                    nickname = nickname_array[1];
                    var nickname_message = "Client " + old_nickname + " changed to " + nickname;
                    wsSend("nick_update", client_uuid, nickname, nickname_message);
                }
            } else {
                wsSend("message", client_uuid, nickname, message);
            }
        });

    处理连接关闭:

      var closeSocket = function(customMessage) {
            for (var i = 0; i < clients.length; i++) {
                if (clients[i].id == client_uuid) {
                    var disconnect_message;
                    if (customMessage) {
                        disconnect_message = customMessage;
                    } else {
                        disconnect_message = nickname + " has disconnected";
                    }
                    wsSend("notification", client_uuid, nickname, disconnect_message);
                    clients.splice(i, 1);
                }
            }
        };
        ws.on('close', function () {
            closeSocket();
        });

    4.客户端

     没有启动时,页面如下,change按钮用来修改昵称。

    <div class="vertical-center">
            <div class="container">
                <ul id="messages" class="list-unstyled"></ul>
                <hr/>
                <form role="form" id="chat_form" onsubmit="sendMessage(); return false;">
                    <div class="form-group">
                        <input class="form-control" type="text" id="message" name="message"
                               placeholder="Type text to echo in here" value="" autofocus/>
                    </div>
                    <button type="button" id="send" class="btn btn-primary"
                            onclick="sendMessage();">
                        Send Message
                    </button>
    
                </form>
                <div class="form-group"><span>nikename:</span><input id="name" type="text" /> <button class="btn btn-sm btn-info" onclick="changName();">change</button></div>
            </div>
        </div>
    View Code

    js:

       //建立连接
            var ws = new WebSocket("ws://localhost:8181");
            var nickname = "";
            ws.onopen = function (e) {
                console.log('Connection to server opened');
            }
            //显示
            function appendLog(type, nickname, message) {
                if (typeof message == "undefined") return;
                var messages = document.getElementById('messages');
                var messageElem = document.createElement("li");
                var preface_label;
                if (type === 'notification') {
                    preface_label = "<span class="label label-info">*</span>";
                } else if (type == 'nick_update') {
                    preface_label = "<span class="label label-warning">*</span>";
                } else {
                    preface_label = "<span class="label label-success">"
                    + nickname + "</span>";
                }
                var message_text = "<h2>" + preface_label + "&nbsp;&nbsp;"
                + message + "</h2>";
                messageElem.innerHTML = message_text;
                messages.appendChild(messageElem);
            }
            //收到消息处理
            ws.onmessage = function (e) {
                var data = JSON.parse(e.data);
                nickname = data.nickname;
                appendLog(data.type, data.nickname, data.message);
                console.log("ID: [%s] = %s", data.id, data.message);
            }
            ws.onclose = function (e) {
                appendLog("Connection closed");
                console.log("Connection closed");
            }
            //发送消息
            function sendMessage() {
                var messageField = document.getElementById('message');
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(messageField.value);
                }
                messageField.value = '';
                messageField.focus();
            }
            //修改名称
            function changName() {
                var name = $("#name").val();
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send("/nick " + name);
                }
            }

     运行结果:

     页面关闭之后,连接马上断开。

     这种实时响应的体验简直不能太爽,代码也清爽了,前端体验也更好,客户端不用一直发请求,服务端不用等着被轮询。

     小结:上面例子的代码都很好理解,接下来学习WebSocket协议。

     源码:http://pan.baidu.com/s/1c2FfKbA

     源码:http://pan.baidu.com/s/1o8KRmUQ  加入了socket.io的实现。

     API:WebSocket API

     相关:websoket使用Protocol Buffers3.0传输

  • 相关阅读:
    详细版Jmeter随机参数的接口并发测试总结
    Windows下MQTT代理服务器的搭建
    关于使用elascticsearch的两个小技巧
    解决easyswoole的swServer_start_check: onTask event callback must be set at报错
    解决使用宝塔安装的swoole扩展,运行项目出现的3个常见问题
    浅谈一下ThinkPHP5.1实现事务嵌套的特性
    资源出现多个 "Access-Control-Allow-Origin"
    Mac 制作系统启动盘
    深入剖析分布式一致性共识算法
    分布式系统限流算法分析与实现
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/5402311.html
Copyright © 2011-2022 走看看