Socket.io 教程,不是聊天应用(使用 React.js)
Socket.io 教程,不是聊天应用(使用 React.js)
Socket.io 教程,不是聊天应用(使用 React.js)
最近,一位朋友咨询如何在仪表盘上实时(或尽可能接近实时)显示传感器数据。具体来说,他要读取一辆直线加速赛车的 OBD2 数据,并希望将一些数据点显示到外部系统。在考虑过 Kafka 这样的消息总线之后,我们觉得这太过分了。我建议使用 WebSocket,特别是 Socket.io 的实现。说实话,我从未使用过 Socket.io 库,只是在书上读到过,而且在寻找如何在聊天应用或多人游戏之外使用它们的信息时,我遇到了意想不到的困难。对我来说,将Socket.io上的聊天室应用教程转换成不同的用例需要一些时间,所以我决定写一个小演示,或许能帮助到那些以不同方式思考套接字的人。
3层
对于我们的原型,我们将模拟传感器/读取器,设置后端服务器作为直通,并实现前端消费者。
后端
我们需要的两个包是:
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
//Port from environment variable or default - 4001
const port = process.env.PORT || 4001;
//Setting up express and adding socketIo middleware
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
//Setting up a socket with the namespace "connection" for new sockets
io.on("connection", socket => {
console.log("New client connected");
//Here we listen on a new namespace called "incoming data"
socket.on("incoming data", (data)=>{
//Here we broadcast it out to all other sockets EXCLUDING the socket which sent us the data
socket.broadcast.emit("outgoing data", {num: data});
});
//A special namespace "disconnect" for when a client disconnects
socket.on("disconnect", () => console.log("Client disconnected"));
});
server.listen(port, () => console.log(`Listening on port ${port}`));
让我们分解一下:
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
//Port from environment variable or default - 4001
const port = process.env.PORT || 4001;
//Setting up express and adding socketIo middleware
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
如果你以前用过 express,那么大部分内容都没什么新意。这里唯一与 socket.io 相关的东西就是const io = socketIo(server);
设置了一个新的 socket.io 服务器实例。
//Setting up a socket with the namespace "connection" for new sockets
io.on("connection", socket => {
console.log("New client connected");
//Here we listen on a new namespace called "incoming data"
socket.on("incoming data", (data)=>{
//Here we broadcast it out to all other sockets EXCLUDING the socket which sent us the data
socket.broadcast.emit("outgoing data", {num: data});
});
//A special namespace "disconnect" for when a client disconnects
socket.on("disconnect", () => console.log("Client disconnected"));
});
server.listen(port, () => console.log(`Listening on port ${port}`));
这里我们设置了一个名为 的套接字命名空间,connection
客户端将连接到该命名空间。初始连接建立后,我们将监听两个新的命名空间incoming data
和disconnect
。第一个命名空间也是我们的“生产者”或传感器/读取器将推送数据的地方。
在回调中,我们调用socket.broadcast.emit("outgoing data", {num: data});
。广播标志很特殊,因为它允许我们向所有客户端(除了发送数据的客户端)发送数据。将数据发送回生产者毫无意义,因此我们在另一个命名空间 上进行广播outgoing data
。
您会注意到,我们在将数据推送到outgoing data
命名空间之前对其进行了序列化。这将使前端更加清晰,并让您了解如何在一次发送中发送多个数据点。
断开连接命名空间是为客户端断开连接而保留的。这里是进行任何清理操作的好地方。例如,如果您的服务器正在跟踪已连接的客户端,那么这里就是将客户端状态更改为断开连接的好地方。
最后一行是设置我们的快速应用程序以开始监听。
模拟传感器
由于这是一个模拟,我们只需要发送一些随机数据。对于原型,这部分是用纯 Node.js 实现的,但 socket.io 有很多客户端库,它们在 Arduino 或其他可以连接 OBD2 传感器的微控制器上运行肯定会更好。别太过苛责我,这只是一个演示。
对于这个演示,我将演示“速度”值。
我们在这里使用的唯一包是socket.io-client。
let socket = require('socket.io-client')('http://127.0.0.1:4001');
//starting speed at 0
let speed = 0;
//Simulating reading data every 100 milliseconds
setInterval(function () {
//some sudo-randomness to change the values but not to drastically
let nextMin = (speed-2)>0 ? speed-2 : 2;
let nextMax = speed+5 < 140 ? speed+5 : Math.random() * (130 - 5 + 1) + 5;
speed = Math.floor(Math.random() * (nextMax - nextMin + 1) + nextMin);
//we emit the data. No need to JSON serialization!
socket.emit('incoming data', speed);
}, 100);
其中大部分内容应该很容易理解,所以本节会比较简短。let socket = require('socket.io-client')('http://127.0.0.1:4001');
设置要使用的包。我们首先将速度变量设置为 0。
let socket = require('socket.io-client')('http://127.0.0.1:4001');
返回要使用的套接字连接。我们告诉它它在何处运行以及在哪个端口上运行。
我setInterval
在这里每 100 毫秒模拟一次微控制器和传感器之间的读取请求。设置下一个速度的数学方法只是一种“黑客”方法,每次都会稍微增加或减少速度,并且不允许速度超过 140 或低于 0。
socket.emit('incoming data', speed);
是我们通过套接字发送数据的地方。我们将数据发送到incoming data
上一节中在后端设置的命名空间中。
就是这样!很酷吧?
仪表板
我用 React 构建了这个,非常简单快捷。我不会深入 React 的细节,因为这超出了本文的范围。我将重点介绍如何从套接字中获取数据。话虽如此,我使用了react-d3-speedometer来显示速度计。不得不说,它的外观真的让我印象深刻!我还使用了我们在生产器上使用的同一个socket.io-client包。
这是 React 组件:
import React, {Component} from "react";
import socketIOClient from "socket.io-client";
import ReactSpeedometer from "react-d3-speedometer"
class App extends Component {
constructor() {
super();
this.state = {
response: 0,
endpoint: "http://127.0.0.1:4001"
};
}
componentDidMount() {
const {endpoint} = this.state;
//Very simply connect to the socket
const socket = socketIOClient(endpoint);
//Listen for data on the "outgoing data" namespace and supply a callback for what to do when we get one. In this case, we set a state variable
socket.on("outgoing data", data => this.setState({response: data.num}));
}
render() {
const {response} = this.state;
return (
<div style={{textAlign: "center"}}>
<ReactSpeedometer
maxValue={140}
value={response}
needleColor="black"
startColor="orange"
segments={10}
endColor="red"
needleTransition={"easeElastic"}
ringWidth={30}
textColor={"red"}
/>
</div>
)
}
}
export default App;
state.response
将保存来自后端的值,并且state.endpoint
指向服务器所在的位置。神奇的事情发生在生命周期函数中componentDidMount()
。对于不熟悉 React 的人来说,这个函数会在组件添加到 DOM 时调用。因此,我们将在这里连接到套接字并监听数据。
const socket = socketIOClient(endpoint);
只需将我们连接到服务器并打开套接字连接。
socket.on("outgoing data", data => this.setState({response: data.num}));
看起来很熟悉,不是吗?我们开始在outgoing data
命名空间上列出。我们有一个回调,它接收响应并将状态设置为新值。
让我们看一下渲染函数:
render() {
const {response} = this.state;
return (
<div style={{textAlign: "center"}}>
<ReactSpeedometer
maxValue={140}
value={response}
needleColor="black"
startColor="orange"
segments={10}
endColor="red"
needleTransition={"easeElastic"}
ringWidth={30}
textColor={"red"}
/>
</div>
)
}
ReactSpeedometer 组件有很多 props,你可以传递给它来自定义它。大部分 props 都一目了然,你也可以在这里阅读全部内容。我使用了needleTransition 的“easeElastic”属性,因为它看起来很酷,但“eastLinear”属性可能更适合用作速度计。点击此处了解过渡效果。
渲染函数提取速度的当前状态值,并将其传递给名为 的 ReactSpeedometer 属性value
。这将更新速度计。
那么它看起来怎么样!
https://i.imgur.com/D4qzm7o.gif
(在此帖子中嵌入 gif 时出现问题。抱歉!)
它最终表现得更像一个转速表,但结果非常酷!
文章来源:https://dev.to/captainpandaz/a-socket-io-tutorial-that-isn-ta-chat-app-with-react-js-58jh