目前做的项目中需要将设备中的实时数据展现到浏览器上,自己查了一些资料,发现用websocket可以满足后端与前端大量数据的传输需求。做了一个简单的demo记录一下。
下图就是后端实时传送上来的数据利用Echart进行展示的效果。
先来实现后端的数据上传逻辑:
后端代码我用的是C# 语言和 Fleck nuget包,还是比较容易建立websocket 服务的。首先我们建立一个websocket server class,设置两个属性,一个是服务器的websocket对象,此对象是为了创建websocket链接,另一个是客户端连到此服务上的socket链接数组。
public class Server
{
private WebSocketServer SocketServer { get; set; }
private List<IWebSocketConnection> ClientSocket { get; set; } = new List<IWebSocketConnection>();
}
接下来我们要写服务端的初始化代码,主要是是初始化websocketServer 对象,让它开始监听,以及当socket开始,关闭时的业务逻辑,我们此时只要在开始,和关闭时把对应的动作打印在console里做个日志输出即可。
public void Start(int port)
{
SocketServer = new WebSocketServer("ws://127.0.0.1:" + port);
SocketServer.Start(socket =>
{
socket.OnOpen = () =>
{
Console.WriteLine("Open!");
ClientSocket.Add(socket);
};
socket.OnClose = () =>
{
ClientSocket.Remove(socket);
Console.WriteLine("Close!");
};
});
完成websocket的初始化建立后,开始写添加收到客户端发来的消息,并处理消息的逻辑代码。我是想当客户端索要数据时,我会不停的给它发送实时数据,直到客户端喊停为止。所以需要先创建一个方法能生成实时数据,因为想在echart里展示,我就做两个list,分别代表x轴和y轴。还需要一个把数据序列化的方法,这样传送时传送序列化后的数据效率会有提升。
数据结构很简单:
public class Data
{
public List<int> x { get; set; }
public List<int> y { get; set; }
}
做序列化的代码:
private string ConvertObjectToString(Data obj)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
MemoryStream memoryStream = new MemoryStream();
serializer.WriteObject(memoryStream, obj);
string json = Encoding.Default.GetString(memoryStream.ToArray());
return json;
}
之后用random对象去生成些实时数据填进去:
private void ProcessClientRequest(){
while (true)
{
Thread.Sleep(300);
Data a = new Data()
{
x = new List<int>(),
y = new List<int>()
};
Random r = new Random();
for (int i = 1; i < 100; i++)
{
a.x.Add(i);
a.y.Add(r.Next(1, 100));
}
ClientSocket.ToList().ForEach(s => s.Send(ConvertObjectToString(a)));
}
}
这样一来我们就可以无限的去生成数据,并通过客户端的连接数组发给所有链接我们的客户端了。但还有一点,我们想要让客户端控制数据开始和停止传送,目前无法停止。我想的是把传送数据代码段另开一个线程,不影响我们的服务器继续接受客户端指令。我们新建一个属性,来存放是否停止,我把此属性命名为IsStop:
socket.OnMessage = message =>
{
Console.WriteLine(message);
Thread thread1 = new Thread(ProcessClientRequest);
thread1.Start(message);
};
首先把监听客户端信息的代码实现,我们另开辟一个线程去处理它,具体的处理也需要做一些变化,先是根据指令检查是否是stop ,如果是将IsStop赋值为true,这时另一个线程中也收到了变为true的信号,便停止了发送。当然这里有个漏洞,那就是如果前端一直发送开始发送的指令,就会有越来越多的线程给前端发送数据,不过我打算忽略它~哈哈哈:
private bool IsStop { get; set; }
private void ProcessClientRequest(object obj)
{
string cmd = obj.ToString();
if (cmd !== "STOP")
{
IsStop = false;
while (!IsStop)
{
Thread.Sleep(300);
Data a = new Data()
{
x = new List<int>(),
y = new List<int>()
};
Random r = new Random();
for (int i = 1; i < 100; i++)
{
a.x.Add(i);
a.y.Add(r.Next(1, 100));
}
ClientSocket.ToList().ForEach(s => s.Send(ConvertObjectToString(a)));
}
}
else
{
IsStop = true;
}
}
整体的服务端代码就出来了:
public class Server
{
private WebSocketServer SocketServer { get; set; }
private List<IWebSocketConnection> ClientSocket { get; set; } = new List<IWebSocketConnection>();
private bool IsStop { get; set; }
public void Start(int port)
{
SocketServer = new WebSocketServer("ws://127.0.0.1:" + port);
SocketServer.Start(socket =>
{
socket.OnOpen = () =>
{
Console.WriteLine("Open!");
ClientSocket.Add(socket);
};
socket.OnClose = () =>
{
ClientSocket.Remove(socket);
Console.WriteLine("Close!");
};
socket.OnMessage = message =>
{
Console.WriteLine(message);
Thread thread1 = new Thread(ProcessClientRequest);
thread1.Start(message);
};
});
while (true)
{
}
}
private void ProcessClientRequest(object obj)
{
string cmd = obj.ToString();
if (cmd == "ACD")
{
IsStop = false;
while (!IsStop)
{
Thread.Sleep(300);
Data a = new Data()
{
x = new List<int>(),
y = new List<int>()
};
Random r = new Random();
for (int i = 1; i < 100; i++)
{
a.x.Add(i);
a.y.Add(r.Next(1, 100));
}
ClientSocket.ToList().ForEach(s => s.Send(ConvertObjectToString(a)));
}
}
else
{
IsStop = true;
}
}
private string ConvertObjectToString(Data obj)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
MemoryStream memoryStream = new MemoryStream();
serializer.WriteObject(memoryStream, obj);
string json = Encoding.Default.GetString(memoryStream.ToArray());
return json;
}
}
//////////////////////////////////////////////////////////////////////
static void Main(string[] args)
{
Server server = new Server();
server.Start(8800);
}
完成后端的数据上传逻辑之后开始编写前端逻辑代码:
前端代码比较简单我用的是echart.js和javaScript 。首先写一个简单的html,将echart的容器定义出来:
<!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">
<script src="./echarts.js"></script>
<script src="./main.js" defer></script>
<title>data from websocket</title>
</head>
<body>
<div id="main-chart" style="width: 600px;height:400px;"></div>
<button class="btn_close">close</button>
</body>
</html>
然后便是JS,需要定义一些echart的配置,以及设置连接后端的代码和更新图表的逻辑:
let myChart = echarts.init(document.getElementById('main-chart'));
let option = {
title: {
text: 'ECharts real time chart!!!'
},
tooltip: {},
legend: {
data: ['y']
},
xAxis: {
data: []
},
yAxis: {},
series: [
{
name: 'y',
type: 'bar',
data: []
}
]
};
let socket = new WebSocket('ws://127.0.0.1:8800');
socket.onopen = function(event)
{
socket.send("ACD");
}
socket.onmessage = function(event)
{
let data = JSON.parse(event.data);
option.xAxis.data = data.x;
option.series[0].data = data.y;
myChart.setOption(option);
}
socket.onclose = function(event)
{
console.log("socket closed");
}
let btn = document.getElementsByClassName('btn_close');
btn[0].addEventListener("click",function(){socket.send("STOP");});
ps 我发现当我的发送间隔极小的时候,比如50ms以下,echart的更新就有些跟不上了。。。所以我选择间隔几百毫秒发送一次。