一、目录
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 使用方法
- 客户端 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
}) ;
- 服务器端 常用的 Node 实现有以下三种。
- WebSockets
- Socket.IO
- WebSocket-Node
具体的用法请查看它们的文档本次实验采用一款简单的开源 WebSocket 服务器:Websocketd。特点:后
台脚本不限语言,标准输入(stdin)就是 WebSocket 的输入,标准输出(stdout)就是 WebSocket 的输出。
3. 实验步骤
3.1 websocket 实验
- 下载并解压 WebSocketd websocketd 网址:http://websocketd.com/websocketd 支持多种语言,可以将你自己的程序整合到服务器中与浏览器中通讯
- 编写一个你自己的程序,以供 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
- 编写客户端 html 文件与服务器通讯
-
编写 counter.html, 代码如下
- 其中,脚本程序的几个事件,当 websocket 服务连接成功则在网页上记录“CONNECT”,断开连
接事件触发记录”DISCONNECT“,有消息更新后将消息记录。
-
使用浏览器打开网页,查看运行结果
image.png -
其他例子 也可使用 javaScript 编写回声服务
image.png
启动该脚本的命令如下:
websocketd −−port =8080 node . / g r e e t e r . j s
3.2 Node-red websocket 实验
-
使用 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.下载安装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

- 编写客户端 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“,有消息更新后将消息记录。
总体效果如下

最终文件如下

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

- 其他例子 也可使用 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 界面
- 使用 websocket in 节点和 websocket out 节点搭建服务端
-
Node red 中的 websocket in 和 websocket out 节点可以搭建成服务端
按下图组建服务端,节点包括 inject 节点、函数节点、websocket in、websocket out 和调试节点
image.png
image.png
• inject 节点配置设置每隔 5 秒更新时间


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


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



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



中间网页模板程序设置如下:
<!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>

- 使用浏览器访问网页,查看部署后的效果
部署后 访问该连接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"
}
]














