websocket 学习--简单使用,nodejs搭建websocket服务器,到模拟股票,到实现聊天室

websocket简介:

WebSocket协议是 HTML5 开始提供的一种基于TCP的一种新的全双工通讯的网络通讯协议。它允许服务器主动发送信息给客户端

 

 

和http协议的不同??

HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。而这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。

简单的说,WebSocket协议之前,实现双工通信就是通过不停发送HTTP请求(长轮询,使用 Ajax 轮询技术,轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求),从服务器拉取更新来实现,这导致了效率低下,浪费带宽资源,WebSocket解决了这个问题。

WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

 

 

websocket如何工作??

在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为"握手" 。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输相传送。

 

 

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

 

以下 API 用于创建 WebSocket 对象。第一个参数 url, 指定连接的 URL。而URL参数需要以WS://或者WSS://开头,例如:ws://www.websocket.org,如果URL有语法错误,构造函数会抛出异常。第二个参数 protocol 是可选的,指定了可接受的子协议。议的参数例如XMPP(Extensible Messaging and Presence Protocol)、SOAP(Simple Object Access Protocol)或者自定义协议。  第二个参数是协议名称,是可选的,服务端和客服端使用的协议必须一致,这样收发消息彼此才能理解,你可以定义一个或多个客户端使用的协议,服务端会选择一个来使用,一个客服端和一个服务端之间只能有一个协议

var Socket = new WebSocket(url, [protocol] );

注意:基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。

目前大部分浏览器支持 WebSocket() 接口,如 Chrome, Mozilla, Opera 和 Safari。

 

 

WS和WSS的区别??

注意:WebSocket协议定义了两种URL方案,WS和WSS分别代表了客户端和服务端之间未加密和加密的通信。WS(WebSocket)类似于Http URL,而WSS(WebSocket Security)URL 表示连接是基于安全传输层(TLS/SSL)和https的连接是同样的安全机制。

 

 

 

websocket的属性、事件、方法

属性 描述
Socket.readyState

只读属性 readyState 表示连接状态,可以是以下值:

0 - 表示连接尚未建立。

1 - 表示连接已建立,可以进行通信。

2 - 表示连接正在进行关闭。

3 - 表示连接已经关闭或者连接不能打开。

Socket.bufferedAmount

只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

注意:上述readyState 属性用于表示链接状态!

 

事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发

 

方法 描述
Socket.send()

使用连接发送数据

Socket.close()

关闭连接

 

 

 

websocket+nodejs简单实例应用

WebSocket 协议本质上是一个基于 TCP 的协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

WebSocket API是纯事件驱动,一旦建立全双工连接,当服务端给客户端发送数据或者资源,它能自动发送状态改变的数据和通知。所以你不需要为了状态的更新而去轮训Server,在客户端监听即可。

 

websocket客户端:

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>websocket测试(runoob.com)</title>
    
      <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的浏览器支持 WebSocket!");
               
               // 初始化一个 WebSocket 对象,参数指明url
               var ws = new WebSocket("ws://localhost:9999");
                
               // WebSocket 连接时候触发
               ws.onopen = function()
               {
                  //使用 send() 方法发送数据
                  ws.send("客户端发送的数据");
                  alert("数据发送中...");
               };
               
               // 接收服务端数据时触发
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                   console.log(received_msg);
                  alert("数据已接收...");
               };
               
               //断开 web socket 连接成功触发事件
               ws.onclose = function()
               { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
            }
            
            else
            {
               // 浏览器不支持 WebSocket
               alert("您的浏览器不支持 WebSocket!");
            }
         }
      </script>
        
   </head>
   <body>
   
      <div id="sse">
         <a href="javascript:WebSocketTest()">运行 WebSocket</a>
      </div>
      
   </body>
</html>

 

websocket服务端:

WebSocket 在服务端的实现非常丰富。Node.js、Java、C++、Python 等多种语言都有自己的解决方案。这里主要记录nodejs作为websocket服务端的解决方案。

Node 实现有以下三种。

 

这里主要记录使用nodejs搭建websocket服务器的方案

ws 是nodejs的一个WebSocket库,可以用来创建服务。使用cnpm install ws 命令行进行安装

下面是server.js的文件内容,cmd转到文件目录运行 node server.js  命令行

 

var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({ port: 9999 });
wss.on('connection', function (ws) {
    console.log('client connected');
    ws.on('message', function (message) {
        console.log(message);
		ws.send("服务端接收到请求后,发送给客户端的数据");
    });
	
});

效果如下:

 

 

 

进阶:websocket+nodejs模拟股票实例

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

注意:一定要先在项目文件夹下运行 cnpm install ws  安装wwebsocket依赖包,不然会报以下错误

 

 

服务端server.js文件内容如下:

//引入websocket 的ws模块
var WebSocketServer = require('ws').Server,

//初始化websocket对象
wss = new WebSocketServer({ port: 8181 });



//初始数据对象
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();

//声明clientStocks接收客户端数据
var clientStocks = [];

//连接建立后
wss.on('connection', function (ws) {

	//定义数据更新函数
    var sendStockUpdates = function (ws) {
        if (ws.readyState == 1) {  //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);
    });
 
});

 

客户端client.html 文件如下,界面使用了和jquery和bootstrape框架

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>WebSocket  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="../bootstrap-3.3.5/js/bootstrap.min.js"></script>
</head>

<body>
<div class="vertical-center">
    <div class="container">
        <h1>Stock Chart over WebSocket</h1>
        <button class="btn btn-primary">开始</button>
        <button class="btn btn-danger">停止</button>
        <table class="table" id="stockTable">
            <thead>
            <tr>
                <th>Symbol</th>
                <th>Price</th>
            </tr>
            </thead>
            <tbody id="stockRows">
            <tr>
                <td>
                    <h3>AAPL</h3></td>
                <td id="AAPL">
                    <h3><span class="label label-default">95.00</span></h3>
                </td>
            </tr>
            <tr>
                <td>
                    <h3>MSFT</h3></td>
                <td id="MSFT">
                    <h3><span class="label label-default">50.00</span></h3>
                </td>
            </tr>
            <tr>
                <td>
                    <h3>AMZN</h3></td>
                <td id="AMZN">
                    <h3><span class="label label-default">300.00</span></h3>
                </td>
            </tr>
            <tr>
                <td>
                    <h3>GOOG</h3></td>
                <td id="GOOG">
                    <h3><span class="label label-default">550.00</span></h3>
                </td>
            </tr>
            <tr>
                <td>
                    <h3>YHOO</h3></td>
                <td id="YHOO">
                    <h3><span class="label label-default">35.00</span></h3>
                </td>
            </tr>
            </tbody>
        </table>
    </div>
</div>
    <script>
	//客户端初始化websocket对象
    var ws = new WebSocket("ws://localhost:9999");

	//客户端发送的请求对象
    var stock_request = { "stocks": ["AAPL", "MSFT", "AMZN", "GOOG", "YHOO"] };

    var isClose = false; //通讯连接是否被关闭

	//界面的初始化数据对象
    var stocks = {
        "AAPL": 0, "MSFT": 0, "AMZN": 0, "GOOG": 0, "YHOO": 0
    };

	//定义更新UI界面的函数
    function updataUI() {
		//websocket连接上时触发
        ws.onopen = function (e) {
            console.log('Connection to server opened');
            isClose = false;
            ws.send(JSON.stringify(stock_request));
            console.log("sened a mesg");
        }

        // UI update function
        var changeStockEntry = function (symbol, originalValue, newValue) {
            var valElem = $('#' + symbol + ' span');
            valElem.html(newValue.toFixed(2)); //toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。
            if (newValue < originalValue) {
                valElem.addClass('label-danger');
                valElem.removeClass('label-success');
            } else if (newValue > originalValue) {
                valElem.addClass('label-success');
                valElem.removeClass('label-danger');
            }
        }

        // websocket接收到服务端数据时触发
        ws.onmessage = function (e) {
            var stocksData = JSON.parse(e.data);  //字符串转JSON对象
            console.log(stocksData);
            for (var symbol in stocksData) {  //遍历对象属性,更改客户端界面数据
                if (stocksData.hasOwnProperty(symbol)) {
                    changeStockEntry(symbol, stocks[symbol], stocksData[symbol]);
                    stocks[symbol] = stocksData[symbol];
                }
            }
        };
    }

    updataUI();  //更新UI界面

    $(".btn-primary").click(function() {  //开始按钮点击可以在断开后重连websocket
        if (isClose) {
            ws = new WebSocket("ws://localhost:9999");
        }
        updataUI();  //重连后更新UI界面
    });

    $(".btn-danger").click(function() {  //断开按钮可以关闭websocket连接
        ws.close();
    });

	//触发websocket连接关闭事件
    ws.onclose = function (e) {
        console.log("Connection closed", e);
        isClose = true;
    };
   

    </script>
</body>
</html>

源码见文章底部链接,效果如下:

 

 

 

进阶:websocket+nodejs模拟聊天室实例

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

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

2、客户端代码如下:

<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");
        var nickname = "";
        ws.onopen = function (e) {
            console.log('Connection to server opened');
        }
        //显示函数,根据客户端接收到的数据类型进行ui界面显示
        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);
            }
        }

        function disconnect() {
            ws.close();
        }
    </script>
</head>

<body >
    <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>
</body>
</html>

3、服务端代码如下:

//引入ws模块,初始化websocket服务端
var WebSocket = require('ws');
var WebSocketServer = WebSocket.Server,
wss = new WebSocketServer({ port: 8181 });

//引入node-uuid模块,唯一标识
var uuid = require('node-uuid');

//客户端数组
var clients = [];

//遍历所有客户端连接,依次下发数据
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({ //websocket传递JSONA字符串格式
                "type": type,
                "id": client_uuid,
                "nickname": nickname,
                "message": message
            }));
        }
    }
}

var clientIndex = 1;

//每一个客户端和服务端建立连接时触发
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) { //json字符串数据包含修改昵称的数据时
            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);
        }
    });
	
	//断开指定uuid的连接
    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();
    });
	
	//SIGINT这个信号是系统默认信号,代表信号中断,就是ctrl+c
    process.on('SIGINT', function () {
        console.log("Closing things");
        closeSocket('Server has disconnected');
        process.exit();
    });
});

效果如下:

源码见文章尾部

 

上述代码实现了一个服务器下的多个客户端连接,单并没有实现客户端的及时通讯,比如微信和QQ的单聊和群聊效果,可以参考以下demo

参考网址:https://blog.csdn.net/CJXBShowZhouyujuan/article/details/77816944

 

 

 

node-uuid是什么??

nodejs生成UID(唯一标识符)——node-uuid模块

unique identifier 惟一标识符        -->> uid

在项目开发中我们常需要给某些数据定义一个唯一标识符,便于寻找,关联。node-uuid模块很好的提供了这个功能。

 

使用起来很简单,两种:

1、uuid.v1(); -->基于时间戳生成  (time-based)

2、uuid.v4(); -->随机生成  (random)

 

通常我们使用基于时间戳  v1()  生成的UID,随机生成  v4()  还是有一定几率重复的。

    var UUID = require('uuid');
    var ID = UUID.v1();

 

 

websocket 和 socket 的区别??

软件通信有七层结构,下三层结构偏向与数据通信,上三层更偏向于数据处理,中间的传输层则是连接上三层与下三层之间的桥梁,每一层都做不同的工作,上层协议依赖与下层协议。基于这个通信结构的概念。

Socket 其实并不是一个协议,是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口。当两台主机通信时,让 Socket 去组织数据,以符合指定的协议。TCP 连接则更依靠于底层的 IP 协议,IP 协议的连接则依赖于链路层等更低层次。

WebSocket 则是一个典型的应用层协议。

总的来说:Socket 是传输控制层协议,WebSocket 是应用层协议。

 

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

或 百度链接:https://pan.baidu.com/s/1cabjJKikHC3xBW-qUtP-3g   提取码:yb4u 
 

参考网址:

https://www.cnblogs.com/stoneniqiu/p/5402311.html

http://www.runoob.com/html/html5-websocket.html

猜你喜欢

转载自blog.csdn.net/zhouzuoluo/article/details/89312798