關於頁面資料更新websocket 紀要
阿新 • • 發佈:2020-07-30
從GitHub 找了原始碼包
WebSocketDemo_CSharpMVC-master.zip
原始碼是個是一個webchat縮減版,程式碼簡潔, 有一點就是非同步的傳送接收,感覺需要深刻理解awaitasync ,裡邊建好了 使用者websocket字典,方便資料從伺服器返回客戶端資料.同時加了離線訊息,這個感覺木大有卵用,還需進一步貫通程式碼
前端我是用在一個動態載入line chart 上,開啟頁面時先載入24小時內資料, 根據資料返回資料把裝置記錄list <string>為value,加使用者名稱為key到字典中,裝置有新資料到來時,通過裝置字典找到裝置,通過裝置找到使用者名稱,知道了使用者名稱,就可以
查詢使用者字典中的連結物件,通過物件destSocket就可以 傳送及時訊息給當前使用者.
用websocketawait destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
邏輯就這些, 從前端chart 到後臺還是除錯了一段時間
@{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_LayoutList.cshtml"; } <!--模糊搜尋區域--> <div class="layui-row"> <form class="layui-form layui-col-md12 ok-search"> <div class="layui-input-inline"> <input class="layui-input" placeholder="請輸入Imei" name="SourceDataIMEI" id="SourceDataIMEI" autocomplete="off"> </div> <div class="layui-input-inline"> <select id="caijidianselect" placeholder="請選擇採集點"> <option value='' disabled selected style='display:none;'>請選擇採集點</option> </select> </div> <button class='layui-btn ' lay-submit='' lay-filter='search'> <i class='layui-icon'></i>查詢 </button> <button type='reset' id='reset' class='layui-btn layui-btn-primary'>重置</button> <label id="labelmess"></label> </form> </div> <script src="~/Content/Scripts/js/jquery.min.js"></script> <script src="~/Content/js/moment.min.js"></script> <script src="~/Content/js/Chart.js"></script> @*<script src="~/Content/js/utils.js"></script> *@ <div class="layui-row " width="1000"> <canvas id="myChart" width="500" height="250"></canvas> <br /> </div> <script> layui.use(["form"], function () { let form = layui.form; let $ = layui.$; //初始化選擇框 initCaijiName(); form.on('submit(search)', function (data) { console.log(data); $.ajax({ url: '/AutoNetwork/ChartsLocations/GetSerachData', type: 'GET', data: { keyword: $('#SourceDataIMEI').val() , StartEndDate: $('#StartEndDate').val() , caijidian: $('#caijidianselect option:selected').val() }, success: function (data1) { console.log(data1); if (data1 == "") { if (config.data.datasets.length > 0) { // 清空 config.data.datasets.splice(0, config.data.datasets.length); window.myLine.update(); } alert("沒有相關資料"); return false; } // console.log(data1); if (config.data.datasets.length > 0) { // 清空 config.data.datasets.splice(0, config.data.datasets.length); window.myLine.update(); } //根據數量新建datasets var termlist = data1.Terminal_lst; // console.log(termlist); for (var i = 0; i < termlist.length; i++) { var colorName = colorNames[config.data.datasets.length % colorNames.length] var newColor = window.chartColors[colorName]; var newDataset = { label: '終端:' + termlist[i].DataIMEI, borderColor: newColor, backgroundColor: color(newColor).alpha(0).rgbString(), data: [], }; var tvlst = termlist[i].TimeAndValue_lst for (var j = 0; j < tvlst.length; j++) { // console.log(tvlst[i]) newDataset.data.push(tvlst[j]); } // console.log(da); config.data.datasets.push(newDataset); window.myLine.update(); } } , error: function (err) { // console.log(err.statusText); console.log('異常'); } }); return false; }); }); //chart area var timeFormat = 'YYYY/MM/DD HH:mm:ss'; function newDate(days) { // console.log(moment().add(days, 'h').toDate()) return moment().add(days, 'h').toDate(); } function newDateString(days) { // console.log(moment().add(days, 'h').format(timeFormat)) return moment().add(days, 'h').format(timeFormat); } var color = Chart.helpers.color; var config = { type: 'line', data: { datasets: [ ] }, options: { title: { text: 'Chart.js Time Scale' }, scales: { xAxes: [{ type: 'time', time: { parser: timeFormat, // round: 'day', // quarter: 'MMM YYYY' tooltipFormat: 'll h:mm:ss a' }, scaleLabel: { display: true, labelString: 'Date' } }], yAxes: [{ scaleLabel: { display: true, labelString: 'value' } }] }, } }; window.onload = function () { var ctx = document.getElementById('myChart').getContext('2d'); window.myLine = new Chart(ctx, config); }; var colorNames = Object.keys({ red: 'rgb(255, 99, 132)', orange: 'rgb(255, 159, 64)', yellow: 'rgb(255, 205, 86)', green: 'rgb(75, 192, 192)', blue: 'rgb(54, 162, 235)', purple: 'rgb(153, 102, 255)', grey: 'rgb(201, 203, 207)', SaddleBrown: 'rgb(139 69 19)', DarkCyan: 'rgb(0 139 139)' }); window.chartColors = { red: 'rgb(255, 99, 132)', orange: 'rgb(255, 159, 64)', yellow: 'rgb(255, 205, 86)', green: 'rgb(75, 192, 192)', blue: 'rgb(54, 162, 235)', purple: 'rgb(153, 102, 255)', grey: 'rgb(201, 203, 207)' }; //連結伺服器 conn(); var ws; //連線 function conn() { //var msg = document.getElementById('msg'); // var user = document.getElementById('user'); ws = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + '/RealTimeData/RealTimeData?username= @ViewBag.userName'); //ws = new WebSocket('ws://localhost:25356/Home/WSChat?user=' + $("#user").val()); console.log('正在連線'); // msg.innerHTML += '<p>正在連線</p>'; ws.onopen = function () { // msg.innerHTML += '<p>已經連線</p>'; console.log('已經連線'); //傳送請求 獲取資料 var content = "realtimedata"; send(content); } ws.onmessage = function (evt) { // msg.innerHTML += '<p>' + evt.data + '</p>'; data1 = evt.data; // console.log(data1); if (data1 == "") { if (config.data.datasets.length > 0) { // 清空 config.data.datasets.splice(0, config.data.datasets.length); window.myLine.update(); } var labelm = document.getElementById('labelmess'); labelm.innerHTML = '24小時內無資料'; // alert("沒有相關資料"); return false; } // console.log(data1); @* if (config.data.datasets.length > 0) { // 清空 config.data.datasets.splice(0, config.data.datasets.length); window.myLine.update(); }*@ //根據數量新建datasets var termlist = JSON.parse(data1).Terminal_lst; // var termlist = data1.Terminal_lst; // console.log(termlist); for (var i = 0; i < termlist.length; i++) { //及時資料到來時,不破壞原圖,在原有線上新增 if (config.data.datasets.length > 0) { for (var j = 0; j < config.data.datasets.length; j++) { if (config.data.datasets[j].label == '終端:' + termlist[i].DataIMEI) { var tvlst = termlist[i].TimeAndValue_lst for (var k = 0; k < tvlst.length; k++) { // console.log(tvlst[i]) config.data.datasets[j].data.push(tvlst[k]); } } } window.myLine.update(); return; } var colorName = colorNames[config.data.datasets.length % colorNames.length] var newColor = window.chartColors[colorName]; var newDataset = { label: '終端:' + termlist[i].DataIMEI, borderColor: newColor, backgroundColor: color(newColor).alpha(0).rgbString(), data: [], }; var tvlst = termlist[i].TimeAndValue_lst for (var j = 0; j < tvlst.length; j++) { // console.log(tvlst[i]) newDataset.data.push(tvlst[j]); } // console.log(da); config.data.datasets.push(newDataset); window.myLine.update(); } } ws.onerror = function (evt) { // msg.innerHTML += '<p>' + JSON.stringify(evt) + '</p>'; console.log(JSON.stringify(evt)) } ws.onclose = function () { console.log('已經關閉'); } } //關閉 function close() { ws.close(); } //傳送 function send( content ) { var to = "@ViewBag.userName"; // var content = "realtimedata"; var msg = document.getElementById('msg'); if (ws.readyState == WebSocket.OPEN) { ws.send(to + "|" + content); } else { // msg.innerHTML = '<p>連線已經關閉</p>'; console.log('已經關閉'); } } //清空 function btn_clear() { var msg = document.getElementById('msg'); msg.innerHTML = ''; } function initCaijiName() { $.ajax({ type: 'GET', url: '/AutoNetwork/ChartsLocations/GetLocations', dataType: "json", success: function (data) { // console.log(data); if (data.length > 0) { var add = document.getElementById("caijidianselect"); for (var i = 0; i < data.length; i++) { var option = document.createElement("option"); option.value = data[i].LocationID; option.innerText = data[i].LocationName; add.append(option); layui.form.render('select') } } } }); } </script> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <title>WebSocket Test</title> </head> <body> <div style="margin-top:50px;text-align:center"> <h1>WebSocket Test</h1> <input type="text" id="user" pattern="使用者名稱稱" /> <input type="button" id="conn" onclick="btn_conn()" value="連線" /> <input type="button" id="close" onclick="btn_close()" value="關閉" /><br /> <input type="text" id="content" pattern="內容" /> <input type="button" id="send" onclick="btn_send()" value="傳送" /> <input type="button" id="clear" onclick="btn_clear()" value="清空" /><br /> <input type="text" id="to" /> :目標使用者 <div id="msg"></div> </div> <script> var ws; //連線 function btn_conn() { var msg = document.getElementById('msg'); var user = document.getElementById('user'); ws = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + '/RealTimeData/RealTimeData'); //ws = new WebSocket('ws://localhost:25356/Home/WSChat?user=' + $("#user").val()); msg.innerHTML += '<p>正在連線</p>'; ws.onopen = function () { msg.innerHTML += '<p>已經連線</p>'; } ws.onmessage = function (evt) { msg.innerHTML += '<p>' + evt.data + '</p>'; } ws.onerror = function (evt) { msg.innerHTML += '<p>' + JSON.stringify(evt) + '</p>'; } ws.onclose = function () { msg.innerHTML += '<p>已經關閉</p>'; } } //關閉 function btn_close() { ws.close(); } //傳送 function btn_send() { var to = "@ViewBag.userName"; var content = "realtimedata"; var msg = document.getElementById('msg'); if (ws.readyState == WebSocket.OPEN) { ws.send(to + "|" + content); } else { msg.innerHTML = '<p>連線已經關閉</p>'; } } //清空 function btn_clear() { var msg = document.getElementById('msg'); msg.innerHTML = ''; } </script> </body> </html>
using AutoNetwork.Services; using AutoNetwork.Web; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.WebSockets; namespace AutoNetwork.Common { public class WebSocket { public WebSocket(){ //定義接收事件 //查詢或者初始化時,把獲取到的terminals 存入list,當終端裝置有資料傳來時,判斷終端有沒有在這list中,如果有則傳送 //及時資訊 } /// <summary> /// 使用者連線池 /// </summary> public static Dictionary<string, System.Net.WebSockets.WebSocket> CONNECT_POOL = new Dictionary<string, System.Net.WebSockets.WebSocket>(); /// <summary> /// 離線訊息池 /// </summary> private static Dictionary<string, List<MessageInfo>> MESSAGE_POOL = new Dictionary<string, List<MessageInfo>>(); //當前被查詢的裝置與使用者 public static Dictionary<string, List<string>> Devices_POOL = new Dictionary<string, List<string>>(); /// <summary> /// 監聽WebSocket /// </summary> /// <param name="context">HttpContext</param> /// <param name="userName">使用者名稱稱</param> /* public void Monitor(HttpContext context ) { if (context.IsWebSocketRequest) { context.AcceptWebSocketRequest((c) => ProcessChat(c,string userName="")); } }*/ /// <summary> /// 監聽WebSocket /// </summary> /// <param name="context">HttpContext</param> /// <param name="userName">使用者名稱稱</param> public void Monitor(HttpContextBase context) { string userName = context.User.Identity.Name; if(userName=="") { return; } if (context.IsWebSocketRequest) { context.AcceptWebSocketRequest((c) => ProcessGet(c,userName)); } } /// <summary> /// 進行恢復 /// </summary> /// <param name="context">AspNetWebSocket上下文</param> /// <param name="userName">使用者暱稱</param> /// <returns></returns> public async Task ProcessGet(AspNetWebSocketContext context, string userName) { System.Net.WebSockets.WebSocket socket = context.WebSocket; try { #region 使用者新增連線池 //第一次 Open 時,新增到連線池中 if (!CONNECT_POOL.ContainsKey(userName)) CONNECT_POOL.Add(userName, socket);//不存在,新增 else if (socket != CONNECT_POOL[userName])//當前物件不一致,更新 CONNECT_POOL[userName] = socket; #endregion 使用者新增連線池 #region 離線訊息處理 if (MESSAGE_POOL.ContainsKey(userName)) { List<MessageInfo> msgs = MESSAGE_POOL[userName]; foreach (MessageInfo item in msgs) { string msgTime = item.MsgTime.ToString("yyyy年MM月dd日HH:mm:ss"); string msgContent = Encoding.UTF8.GetString(item.MsgContent.Array); await socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("時間:" + msgTime + "內容:" + msgContent)), WebSocketMessageType.Text, true, CancellationToken.None); } MESSAGE_POOL.Remove(userName);//移除離線訊息 } #endregion 離線訊息處理 string descUser = string.Empty;//目的使用者 while (true) { if (socket.State == WebSocketState.Open) { ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]); WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None); #region 訊息處理(字元擷取、訊息轉發) try { #region 關閉Socket處理,刪除連線池 if (socket.State != WebSocketState.Open)//連線關閉 { if (CONNECT_POOL.ContainsKey(userName)) CONNECT_POOL.Remove(userName);//刪除連線池 break; } #endregion 關閉Socket處理,刪除連線池 string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);//傳送過來的訊息 string[] msgList = userMsg.Split('|'); if (msgList.Length == 2) { if (msgList[0].Trim().Length > 0) descUser = msgList[0].Trim();//記錄訊息目的使用者 string jsonre=""; //呼叫查詢介面返回json資料 if (msgList[1].Trim() == "realtimedata") { List<string> devlist; jsonre = WebSocketService.GetWebSocketSerachData("","",out devlist); if(devlist.Count>0) { //第一次 Open 時,新增到連線池中 if (!Devices_POOL.ContainsKey(userName)) Devices_POOL.Add(userName, devlist);//不存在,新增 else if (devlist != Devices_POOL[userName])//當前物件不一致,更新 Devices_POOL[userName] = devlist; } //添加當前請求裝置池 // jsonre= JsonConvert.SerializeObject(re); } buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(jsonre)); if (CONNECT_POOL.ContainsKey(descUser))//判斷客戶端是否線上 { System.Net.WebSockets.WebSocket destSocket = CONNECT_POOL[descUser];//目的客戶端 if (destSocket != null && destSocket.State == WebSocketState.Open) await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } else { await Task.Run(() => { if (!MESSAGE_POOL.ContainsKey(descUser))//將使用者新增至離線訊息池中 MESSAGE_POOL.Add(descUser, new List<MessageInfo>()); MESSAGE_POOL[descUser].Add(new MessageInfo(DateTime.Now, buffer));//新增離線訊息 }); } } else { buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMsg)); foreach (KeyValuePair<string, System.Net.WebSockets.WebSocket> item in CONNECT_POOL) { await item.Value.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } } } catch (Exception exs) { //訊息轉發異常處理,本次訊息忽略 繼續監聽接下來的訊息 string message = exs.Message; } #endregion 訊息處理(字元擷取、訊息轉發) } else { break; } } } catch (Exception ex) { string message = ex.Message; //整體異常處理 if (CONNECT_POOL.ContainsKey(userName)) CONNECT_POOL.Remove(userName); } } public static void Send(string username, string result) { SendToClient(username, result); } /// <summary> /// 傳送訊息給客戶端 /// </summary> /// <param name="username"></param> /// <returns></returns> public static async Task SendToClient(string username,string result) { ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]); System.Net.WebSockets.WebSocket destSocket = CONNECT_POOL[username]; if (destSocket != null && destSocket.State == WebSocketState.Open) { buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(result)); await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } } } }
private void ProcessReceivedDataWebsocket(object networkConnectionParameter) { lock (lockProcessReceivedData) { Byte[] data = new byte[((NetConnParam)networkConnectionParameter).bdata.Length]; // if (debug) StoreLogData.Instance.Store("Received Data: " + BitConverter.ToString(bytes), System.DateTime.Now); NetworkStream stream = ((NetConnParam)networkConnectionParameter).stream; int portIn = ((NetConnParam)networkConnectionParameter).portIn; IPAddress ipAddressIn = ((NetConnParam)networkConnectionParameter).ipAddressIn; string sdata = ((NetConnParam)networkConnectionParameter).sdata; Array.Copy(((NetConnParam)networkConnectionParameter).bdata, 0, data, 0, ((NetConnParam)networkConnectionParameter).bdata.Length); //假使 收到了terminal 850001245 溫度值36 時間現在 在此包裝成前端需要 ,呼叫前端使用者在websocket 使用者字典中的 值,檢視當前使用者,如果都存在 //此終端,則呼叫使用者通訊字典,給每個使用者傳送數值資訊. //1: 查詢字典 有無此裝置dev string devimei = "850001245"; if(WebSocket.Devices_POOL.Count>0) { foreach (KeyValuePair<string, List<string>> kvp in WebSocket.Devices_POOL) { for (int i = 0; i < kvp.Value.Count; i++) { //如果裝置imei 在此表中,則找到 使用者名稱,給此使用者名稱更新資訊 if(kvp.Value[i]== devimei) { //判斷使用者是否線上 if (WebSocket.CONNECT_POOL.ContainsKey(kvp.Key)) { //如果線上 打包 傳送指令 TerminalsDataChartModel tdcm = new TerminalsDataChartModel(); List<TerminalData> ltd = new List<TerminalData>(); TerminalData td = new TerminalData(); td.DataIMEI = "850001245"; TimeAndValue tav = new TimeAndValue(); // tav.x = DateTime.Now.ToUniversalTime().ToString(); //item["DataCreateOn"].ToString(); //item.DataCreateOn.ToString(); tav.x = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); tav.y = int.Parse(sdata);//.DataValue; td.TimeAndValue_lst.Add(tav); ltd.Add(td); tdcm.Terminal_lst = ltd; string result= JsonConvert.SerializeObject(tdcm); //傳送 WebSocket.Send(kvp.Key, result); } } } } } } }