1. 程式人生 > 實用技巧 >關於頁面資料更新websocket 紀要

關於頁面資料更新websocket 紀要

從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'>&#xe615;</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);
                                }                                  
                            }                          
                        }                      
                     
                    }
                } 
            }
        }