lab10-网络层设计二: websocket 节点实验

一、目录

1. 实验目的

  • 掌握 websocket 协议的原理以及在 Node-Red 中的使用方式

2. 实验原理

2.1 为什么需要 WebSocket

初次接触 WebSocket 的同学,都会问同样的问题:已经有了 HTTP 协议,为什么还需要另一个协议?
它能带来什么好处?
答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用” 轮询”:每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。

2.2 WebSocket 相关知识

WebSocket 协议在 2008 年诞生,2011 年成为国际标准。所有浏览器都已经支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。


  • WebSocket 的特点:
    – 建立在 TCP 协议之上,服务器端的实现比较容易。
    – 与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
    – 数据格式比较轻量,性能开销小,通信高效。
    – 可以发送文本,也可以发送二进制数据。
    – 没有同源限制,客户端可以与任意服务器通信。
    – 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL。
    ws://example.com:80/some/path

2.3 WebSocket 使用方法

  1. 客户端 API
  • 构造函数 WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
var ws = new WebSocket ( 'ws://localhost:8080') ;

执行上面语句之后,客户端就会与服务器进行连接。

  • webSocket.readyState 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 ;
d e f a u l t :
// t h i s never happens
break ;
}
  • webSocket.onopen 实例对象的 onopen 属性,用于指定连接成功后的回调函数。
ws . onopen = function ( ) {
ws . send ( ' Hello Server ! ' ) ;
}

如果要指定多个回调函数,可以使用 addEventListener 方法

ws . addEventListener ( ' open ' , function ( event ) {
ws . send ( ' Hello Server ! ' ) ;
}) ;
  • webSocket.onclose 实例对象的 onclose 属性,用于指定连接关闭后的回调函数。
ws . onclose = function ( event ) {
var code = event . code ;
var reason = event . reason ;
var wasClean = event . wasClean ;
// handle c l o s e event
} ;
ws . addEventListener ( ” c l o s e ” , function ( event ) {
var code = event . code ;
var reason = event . reason ;
var wasClean = event . wasClean ;
// handle c l o s e event
}) 
  • webSocket.onmessage 实例对象的 onmessage 属性,用于指定收到服务器数据后的回调函数。
ws . onmessage = function ( event ) {
var data = event . data ;
// 处 理 数 据
} ;
ws . addEventListener ( ” message ” , function ( event ) {
var data = event . data ;
// 处 理 数 据
}) ;

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

ws . onmessage = function ( event ) {
i f ( typeof event . data === String ) {
console . log ( ” Received data s t r i n g ” ) ;
}
i f ( event . data i n s t a n c e o f ArrayBuffer ) {
var b u f f e r = event . data ;
console . log ( ” Received a r r a y b u f f e r ” ) ;
}
}

除了动态判断收到的数据类型,也可以使用 binaryType 属性,显式指定收到的二进制数据类型。

// 收 到 的 是 b l o b 数 据
ws . binaryType = ” blob ” ;
ws . onmessage = function ( e ) {
console . log ( e . data . s i z e ) ;
} ;
// 收 到 的 是 ArrayBuffer 数 据
ws . binaryType = ” a r r a y b u f f e r ” ;
ws . onmessage = function ( e ) {
console . log ( e . data . byteLength ) ;
} 
  • webSocket.send 实例对象的 send() 方法用于向服务器发送数据。一个发送文本的例子
ws . send ( ' your message ' ) 

一个发送 Blob 对象的例子

var f i l e = document
. querySelector ( ' input [ type=” f i l e ” ] ' )
. f i l e s [ 0 ] ;
ws . send ( f i l e ) ;

发送 ArrayBuffer 对象的例子

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context . getImageData (0 , 0 , 400 , 320) ;
var binary = new Uint8Array ( img . data . length ) ;
f o r ( var i = 0 ; i < img . data . length ; i++) {
binary [ i ] = img . data [ i ] ;
}
ws . send ( binary . b u f f e r ) ;

  • webSocket.onerror 实例对象的 onerror 属性,用于指定报错时的回调函数
socket . onerror = function ( event ) {
// handle error event
} ;
socket . addEventListener ( ” e r r o r ” , function ( event ) {
// handle error event
}) ;
  1. 服务器端 常用的 Node 实现有以下三种。
  • WebSockets
  • Socket.IO
  • WebSocket-Node
    具体的用法请查看它们的文档本次实验采用一款简单的开源 WebSocket 服务器:Websocketd。特点:后
    台脚本不限语言,标准输入(stdin)就是 WebSocket 的输入,标准输出(stdout)就是 WebSocket 的输出。

3. 实验步骤

3.1 websocket 实验

  1. 下载并解压 WebSocketd websocketd 网址:http://websocketd.com/websocketd 支持多种语言,可以将你自己的程序整合到服务器中与浏览器中通讯
  2. 编写一个你自己的程序,以供 websocketd 调用
  • 创建 Counter.java,实现每隔 500ms 输出一个数字
c l a s s Counter {
public s t a t i c void main ( String [ ] args ) throws Exception {
f o r ( i n t i =0; i <10; i++) {
System . out . p r i n t l n ( i ) ;
Thread . s l e e p (500) ;
}
}
}
  • 使用 javac 编译 Counter.java
javac Counter.java
  • 启动 websocketd 服务器
websocketd -port=8080 java Counter
  1. 编写客户端 html 文件与服务器通讯
  • 编写 counter.html, 代码如下


  • 其中,脚本程序的几个事件,当 websocket 服务连接成功则在网页上记录“CONNECT”,断开连
    接事件触发记录”DISCONNECT“,有消息更新后将消息记录。
  1. 使用浏览器打开网页,查看运行结果


    image.png
  2. 其他例子 也可使用 javaScript 编写回声服务


    image.png

    启动该脚本的命令如下:

websocketd −−port =8080 node . / g r e e t e r . j s

3.2 Node-red websocket 实验

  1. 使用 websocket in 节点和 websocket out 节点搭建服务端
    • Node red 中的 websocket in 和 websocket out 节点可以搭建成服务端


    image.png

    • 按下图组建服务端,节点包括 inject 节点、函数节点、websocket in、websocket out 和调试节点


    image.png

    inject 节点配置设置每隔 5 秒更新时间
    image.png

    • 函数节点将时间格式化成指定的时间格式
    image.png

    websocket 的配置设置如下图,这里注意 websocket 服务器的搭建是在 Node-Red 的端口下的。


    image.png

    使用 http 节点构建客户端,在客户端中通过 javaScript 与 websocket 通信
    image.png

    中间网页模板程序设置如下:
<!DOCTYPE HTML>
<html>
<head>
<t i t l e>Simple Live Display</ t i t l e>
<s c r i p t type=” text / j a v a s c r i p t ”>
var ws ;
var wsUri = ”ws: ” ;
var l o c = window . l o c a t i o n ;
console . log ( l o c ) ;
i f ( l o c . protocol === ” https : ” ) { wsUri = ”wss : ” ; }
// 在 节 点 中 指 向 websocket 的 服 务
// ws/ simple
wsUri += ” // ” + l o c . host + l o c . pathname . r e p l a c e ( ” simple ” , ”ws/
simple ” ) ;
function wsConnect ( ) {
console . log ( ” connect ” , wsUri ) ;
ws = new WebSocket ( wsUri ) ;
ws . onmessage = function (msg) {
var l i n e = ” ” ;
// 将 消 息 处 理 为JSON对 象
var data = msg . data ;
// 构 建 输 出 格 式
l i n e += ”<p>”+data+”</p>” ;
document . getElementById ( ' messages ' ) . innerHTML = l i n e ;
}
ws . onopen = function ( ) {
document . getElementById ( ' status ' ) . innerHTML = ”
connected ” ;
8
《物联网工程设计与实践》广西职业师范学院实验教学指导书
console . log ( ” connected ” ) ;
}
ws . onclose = function ( ) {
document . getElementById ( ' status ' ) . innerHTML = ” not
connected ” ;
setTimeout ( wsConnect ,3000) ;
}
}
// 点 击 按 钮 后 给 websocket 服 务 发 送 消 息
function doit (m) {
i f (ws) { ws . send (m) ; }
}
</ s c r i p t>
</head>
<body onload=”wsConnect ( ) ; ” onunload=”ws. disconnect ( ) ; ”>
<font f a c e=” Arial ”>
<h1>Simple Live Display</h1>
<div id=” messages ”></ div>
<button type=” button ” o n c l i c k =' doit ( ” c l i c k ” ) ; '>Click to send
message</ button>
<hr />
<div id=” st a tu s ”>unknown</ div>
</ font>
</body>
</html>
  1. 使用浏览器访问网页,查看部署后的效果
实现
1.下载安装websocket

进入官网进行下载 http://websocketd.com/,根据自身电脑情况选择合适的版本安装。


下载并解压,安装

2.下载安装Java JDK

下载与安装
自己去官网下载一个,然后打开安装



开始安装,直接下一步



路径可改可不改,不改就下一步,改了之后,也下一步

等待安装1

继续安装下一个,路径可改可不改,不改就下一步,改了之后,也下一步



等待安装2

2个安装完成,然后关闭就行

Java配置环境
右键电脑属性

高级属性设置

环境变量

进入界面,点新建环境变量



填入以下内容,具体如下图
注;此处填写不区分大小写
JAVA_HOME

注;此处填写的是你的安装路径

C:\Program Files\Java\jdk1.8.0_331

双击Path进入,编辑。或者点击编辑按钮进入



点击新建



填入以下信息。具体如下图
%JAVA_HOME%\bin
%JAVA_HOME%\jre\bin

点击确定完成设置



再新增最后一个变量



填入以下信息,具体如下图
classpath
.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar

最后全部点击确定,关闭所有窗口
如果,配置完以上,还有问题的,检查一下是否有这两个文件,在你的安装路径下


3.编写一个你自己的程序,以供 websocketd 调用(这里使用java)
  • 创建 Counter.java,实现每隔 500ms 输出一个数字
class Counter {
    public static void main (String[] args) throws Exception {
        for (int i =0; i <10; i++) 
        {
            System.out.println(i);
            Thread.sleep(500);
        }
    }
}
  • 使用 javac 编译 Counter.java



    使用以下命令编译Counter.java

javac Counter.java
  • 启动 websocketd 服务器



    执行以下命令

websocketd -port=8080 java Counter
  1. 编写客户端 html 文件与服务器通讯
    编写以下代码到后缀为HTML的文件内
<!DOCTYPE htmI>
<pre id="log"></pre> 
<script>
// helper function: 1og message to screen 
function log(msg) {
    document.getElementById('log').textContent += msg + '\n' ;
}
// setup websocket with cal Ibacks 
var Ws = new WebSocket('ws://localhost:8080/');

Ws.onopen = function() {
    log('CONNECT');
};
Ws.onclose = function() {
    log('DISCONNECT');
}
Ws.onmessage = function(event)
{
    log('MESSAGE:' + event .data);
}   

</script>

其中,脚本程序的几个事件,当 websocket 服务连接成功则在网页上记录“CONNECT”,断开连接事件触发记录”DISCONNECT“,有消息更新后将消息记录。

总体效果如下


最终文件如下



使用浏览器打开网页,查看运行结果


  1. 其他例子 也可使用 javaScript 编写回声服务
    具体代码如下
process. stdin. setEncoding('utf8' );

process. stdin. on('readable',function() {
    var chunk = process. stdin.read();
    if (chunk !== null) {
        process. stdout .write( ' data:'+ chunk);
    }
});

启动该脚本的命令如下

websocketd -port=8080 node greeter.js

启动流程



进入cmd 输入命令,回车启动



启动成功

回到HTML文件查看
表示成功,但是没有数据



接着在 greeter.js 内制造数据
for (var i=0;i<10;i++)
{ 
    console.log('测试成功',i);
}

再重新 进入cmd 启动,查看效果




3.2 Node-red websocket 实验
进入Node-red 界面

  1. 使用 websocket in 节点和 websocket out 节点搭建服务端
  • Node red 中的 websocket in 和 websocket out 节点可以搭建成服务端



    按下图组建服务端,节点包括 inject 节点、函数节点、websocket in、websocket out 和调试节点


    image.png

    image.png

• inject 节点配置设置每隔 5 秒更新时间


image.png

image.png

函数节点将时间格式化成指定的时间格式


image.png

image.png

websocket 的配置设置如下图,这里注意 websocket 服务器的搭建是在 Node-Red 的端口下的。


image.png


image.png

使用 http 节点构建客户端,在客户端中通过 javaScript 与 websocket 通信


image.png

image.png

image.png

中间网页模板程序设置如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
    <title>Simple Live Display</title>
    <script>
        var ws;
        var wsUri = "ws:";
        var loc = window.location;
        console.log(loc);
        if(loc.protocol === "https:") 
        {
             wsUri = "wss:"; 
        }
        // 在节点中指向 websocket 的服务
        // ws/simple
        wsUri += "//" +loc.host + loc.pathname.replace("simple", "ws/simple");
        function wsConnect (){
            console.log ("connect",wsUri);
            ws = new WebSocket(wsUri);
            ws.onmessage = function (msg){
                var line = "";
                // 将消息处理为 JSON 对象
                var data = msg.data;
                // 构建输出格式
                line += "<p>"+data+"</p>";
                document.getElementById('messages').innerHTML = line;
            }
            ws.onopen = function () {
                document.getElementById ('status').innerHTML = "connected"
                console.log("connected") ;

            }
            ws.onclose = function () {
                document.getElementById ('status').innerHTML = "not connected";
                setTimeout(wsConnect,3000) ;
            }

        }
        //点击按钮后给 websocket 服务发送消息
        function doit (m) {
            if (ws) {
                ws.send(m); 
            }
        }

    </script>
</head>
<body onload="wsConnect();"onunload="ws.disconnect();">
    <font face="Arial">
        <h1>Simple Live Display</h1>
        <div id="messages"></div>
        <button type="button" onclick ='doit("click");'>Click to send message</button>
        <hr />
        <div id="status">unknown</div>
    </font>
</body>
</html>
  1. 使用浏览器访问网页,查看部署后的效果
    部署后 访问该连接http://localhost:1880/simple
    点击 按钮 即可看见信息

全部代码

[
    {
        "id": "89820e7323c13d96",
        "type": "tab",
        "label": "流程 7",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "30721f8514ef51d3",
        "type": "inject",
        "z": "89820e7323c13d96",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "5",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 80,
        "y": 120,
        "wires": [
            [
                "2472e91602f6ffb6"
            ]
        ]
    },
    {
        "id": "2472e91602f6ffb6",
        "type": "function",
        "z": "89820e7323c13d96",
        "name": "function 18",
        "func": "// @ts-ignore\nmsg.payload = Date(msg.payload).toString();\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 110,
        "y": 180,
        "wires": [
            [
                "75f0bfd83cd0d43f"
            ]
        ]
    },
    {
        "id": "75f0bfd83cd0d43f",
        "type": "websocket out",
        "z": "89820e7323c13d96",
        "name": "",
        "server": "a1ee7dada181269b",
        "client": "",
        "x": 160,
        "y": 260,
        "wires": []
    },
    {
        "id": "3b18802ce2087a95",
        "type": "websocket in",
        "z": "89820e7323c13d96",
        "name": "",
        "server": "a1ee7dada181269b",
        "client": "",
        "x": 140,
        "y": 380,
        "wires": [
            [
                "9818f3c7f07aafef"
            ]
        ]
    },
    {
        "id": "9818f3c7f07aafef",
        "type": "debug",
        "z": "89820e7323c13d96",
        "name": "debug 31",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 120,
        "y": 460,
        "wires": []
    },
    {
        "id": "5658c104ebba4811",
        "type": "http in",
        "z": "89820e7323c13d96",
        "name": "",
        "url": "/simple",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 130,
        "y": 540,
        "wires": [
            [
                "ac32cf67129e4fab"
            ]
        ]
    },
    {
        "id": "ac32cf67129e4fab",
        "type": "template",
        "z": "89820e7323c13d96",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <!-- <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> -->\n    <title>Simple Live Display</title>\n    <script>\n        var ws;\n        var wsUri = \"ws:\";\n        var loc = window.location;\n        console.log(loc);\n        if(loc.protocol === \"https:\") \n        {\n             wsUri = \"wss:\"; \n        }\n        // 在节点中指向 websocket 的服务\n        // ws/simple\n        wsUri += \"//\" +loc.host + loc.pathname.replace(\"simple\", \"ws/simple\");\n        function wsConnect (){\n            console.log (\"connect\", wsUri);\n            ws = new WebSocket (wsUri);\n            ws.onmessage = function (msg){\n                var line = \"\";\n                // 将消息处理为 JSON 对象\n                var data = msg.data;\n                // 构建输出格式\n                line += \"<p>\"+data+\"</p>\";\n                document.getElementById ('messages').innerHTML = line;\n            }\n            ws.onopen = function () {\n                document.getElementById ('status').innerHTML = \"connected\"\n                console.log (\"connected\") ;\n\n            }\n            ws.onclose = function () {\n                document.getElementById ('status').innerHTML = \"not connected\";\n                setTimeout(wsConnect,3000) ;\n            }\n\n        }\n        //点击按钮后给 websocket 服务发送消息\n        function doit (m) {\n            if (ws) {\n                ws.send(m); \n            }\n        }\n\n    </script>\n</head>\n<body onload=\"wsConnect();\"onunload=\"ws.disconnect();\">\n    <font face=\"Arial\">\n        <h1>Simple Live Display</h1>\n        <div id=\"messages\"></div>\n        <button type=\"button\" onclick ='doit(\"click\");'>Click to send message</button>\n        <hr />\n        <div id=\"status\">unknown</div>\n    </font>\n</body>\n</html>",
        "output": "str",
        "x": 110,
        "y": 600,
        "wires": [
            [
                "055cb46e15e1109e"
            ]
        ]
    },
    {
        "id": "055cb46e15e1109e",
        "type": "http response",
        "z": "89820e7323c13d96",
        "name": "",
        "statusCode": "",
        "headers": {},
        "x": 110,
        "y": 660,
        "wires": []
    },
    {
        "id": "a1ee7dada181269b",
        "type": "websocket-listener",
        "path": "/ws/simple",
        "wholemsg": "false"
    }
]
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容