仿微信 即時聊天工具 - SignalR (一)

来源:https://www.cnblogs.com/colyn/archive/2019/12/03/11976006.html
-Advertisement-
Play Games

話不多說,先上圖 背景: 微信聊天,經常會遇見視頻發不了,嗯,還有聊天不方便的問題,於是我就自己買了伺服器,部署了一套可以直接在微信打開的網頁進行聊天,這樣只需要發送個url給朋友,就能聊天了! 由於自己無聊弄著玩的,代碼比較粗糙,各位多指正! 1、首先安裝SignalR,這步我就不做過多說明瞭 安 ...


話不多說,先上圖

 

 

 

 

背景:

微信聊天,經常會遇見視頻發不了,嗯,還有聊天不方便的問題,於是我就自己買了伺服器,部署了一套可以直接在微信打開的網頁進行聊天,這樣只需要發送個url給朋友,就能聊天了!

由於自己無聊弄著玩的,代碼比較粗糙,各位多指正!

1、首先安裝SignalR,這步我就不做過多說明瞭

安裝好以後在根目錄新建一個Hubs文件夾,做用戶的註冊和通知

MessageHub.cs 文件

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace SignalR.Hubs
{
    [HubName("MessageHub")]
    public class MessageHub : Hub
        {
            private readonly ChatTicker ticker;
            public MessageHub()
            {
                ticker = ChatTicker.Instance;
            }

            public void register(string username, string group = "default")
            {
                var list = (List<SiginalRModel>)HttpRuntime.Cache.Get("msg_hs");
                if (list == null)
                {
                    list = new List<SiginalRModel>();
                }
               

                if (list.Any(x => x.connectionId == Context.ConnectionId))
                {
                    Clients.Client(Context.ConnectionId).broadcastMessage("已經註冊,無需再次註冊");
                }
            else if (list.Any(x => x.name == username))
            {
                var model = list.Where(x => x.name == username && x.group == group).FirstOrDefault();
                if (model != null)
                {
                    //註冊到全局  
                    ticker.GlobalContext.Groups.Add(Context.ConnectionId, group);
                    Clients.Client(model.connectionId).exit();
                    ticker.GlobalContext.Groups.Remove(model.connectionId, group);
                    list.Remove(model);
                    model.connectionId = Context.ConnectionId;
                    list.Add(model);
                    Clients.Group(group).removeUserList(model.connectionId);
                    Thread.Sleep(200);
                    var gourpList = list.Where(x => x.group == group).ToList();
                    Clients.Group(group).appendUserList(Context.ConnectionId, gourpList);
                    HttpRuntime.Cache.Insert("msg_hs", list);
                    // Clients.Client(model.connectionId).broadcastMessage("名稱重覆,只能註冊一個");
                }
                //Clients.Client(Context.ConnectionId).broadcastMessage("名稱重覆,只能註冊一個");
            }
            else
                {
                    list.Add(new SiginalRModel() { name = username, group = group, connectionId = Context.ConnectionId });

                    //註冊到全局  
                    ticker.GlobalContext.Groups.Add(Context.ConnectionId, group);
                    Thread.Sleep(200);

                    var gourpList = list.Where(x => x.group == group).ToList();
                    Clients.Group(group).appendUserList(Context.ConnectionId, gourpList);
                    HttpRuntime.Cache.Insert("msg_hs", list);
                }

            }

            public void Say(string msg)
            {
                var list = (List<SiginalRModel>)HttpRuntime.Cache.Get("msg_hs");
                if (list == null)
                {
                    list = new List<SiginalRModel>();
                }
                var userModel = list.Where(x => x.connectionId == Context.ConnectionId).FirstOrDefault();
                if (userModel != null )
                {
                    Clients.Group(userModel.group).Say(userModel.name, msg);
                }
            }

        public void Exit()
        {
            OnDisconnected(true);
        }

        public override Task OnDisconnected(bool s)
                {
                    var list = (List<SiginalRModel>)HttpRuntime.Cache.Get("msg_hs");
                    if (list == null)
                    {
                        list = new List<SiginalRModel>();
                    }
                    var closeModel = list.Where(x => x.connectionId == Context.ConnectionId).FirstOrDefault();

                    if (closeModel != null)
                    {
                        list.Remove(closeModel);

                        Clients.Group(closeModel.group).removeUserList(Context.ConnectionId);

                     }
                    HttpRuntime.Cache.Insert("msg_hs", list);
                
                    return base.OnDisconnected(s);
                }
            }
        

    public class ChatTicker
        {
            #region 實現一個單例

            private static readonly ChatTicker _instance =
                new ChatTicker(GlobalHost.ConnectionManager.GetHubContext<MessageHub>());

            private readonly IHubContext m_context;

            private ChatTicker(IHubContext context)
            {

                m_context = context;
                //這裡不能直接調用Sender,因為Sender是一個不退出的“死迴圈”,否則這個構造函數將不會退出。  
                //其他的流程也將不會再執行下去了。所以要採用非同步的方式。  
                //Task.Run(() => Sender());
            }

            public IHubContext GlobalContext
            {
                get { return m_context; }
            }

            public static ChatTicker Instance
            {
                get { return _instance; }
            }

            #endregion
        }

    public class SiginalRModel {
        public string connectionId { get; set; }

        public string group { get; set; }
        public string name { get; set; }
    }
}

我把類和方法都寫到一塊了,大家最好是分開!

 

接下來是控制器

HomeController.cs

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Client;
using SignalR.Hubs;
using SignalR.ViewModels;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace SignalR.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
    
            return View();
        }


        public ActionResult GetV(string v)
        {
            if (!string.IsNullOrEmpty(v))
            {
                string url = RedisHelper.Get(v)?.ToString();
                if (!string.IsNullOrEmpty(url))
                {
                    return Json(new { isOk = true, m = url }, JsonRequestBehavior.AllowGet);
                }
                return Json(new { isOk = false}, JsonRequestBehavior.AllowGet);
            }
            return Json(new { isOk = false }, JsonRequestBehavior.AllowGet);
        }

        public ActionResult getkey(string url)
        {
            if (!string.IsNullOrEmpty(url))
            {
                var s = "v" + Util.GetRandomLetterAndNumberString(new Random(), 5).ToLower();
                var dt = Convert.ToDateTime(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd 04:00:00"));
                int min = Convert.ToInt16((dt - DateTime.Now).TotalMinutes);
                RedisHelper.Set(s, url, min);
                return Json(new { isOk = true, m = s }, JsonRequestBehavior.AllowGet);
            }
            return Json(new { isOk = false }, JsonRequestBehavior.AllowGet);
        }

        public ActionResult upfile()
        {
            try
            {
                if (Request.Files.Count > 0)
                {
                    var file = Request.Files[0];
                    if (file != null)
                    {
                        var imgList = new List<string>() { ".gif", ".jpg", ".bmp", ".png" };
                        var videoList = new List<string>() { ".mp4" };
                        FileModel fmodel = new FileModel();

                        string name = Guid.NewGuid().ToString();
                        string fileExt = Path.GetExtension(file.FileName).ToLower();//上傳文件擴展名
                        string path = Server.MapPath("~/files/") + name + fileExt;
                        file.SaveAs(path);

                        string extension = new FileInfo(path).Extension;

                        if (extension == ".mp4")
                        {
                            fmodel.t = 2;
                        }
                        else if (imgList.Contains(extension))
                        {
                            fmodel.t = 1;
                        }
                        else
                        {
                            fmodel.t = 0;
                        }
                        string url = Guid.NewGuid().ToString();
                        fmodel.url = "http://" + Request.Url.Host;
                        if (Request.Url.Port != 80)
                        {
                            fmodel.url += ":" + Request.Url.Port;
                        }
                        fmodel.url += "/files/" + name + fileExt;
                        GetImageThumb(Server.MapPath("~") + "files\\" + name + fileExt, name);
                        return Json(new { isOk = true, m = "file:" + JsonConvert.SerializeObject(fmodel) }, JsonRequestBehavior.AllowGet);
                    }
                }
            }
            catch(Exception ex)
            {
                Log.Info(ex);
            }
           
           
            return Content("");
        }

        public string GetImageThumb(string localVideo,string name)
        {
            string path = AppDomain.CurrentDomain.BaseDirectory;
            string ffmpegPath = path + "/ffmpeg.exe";
            string oriVideoPath = localVideo;
            int frameIndex = 5;
            int _thubWidth;
            int _thubHeight;
            GetMovWidthAndHeight(localVideo, out _thubWidth, out _thubHeight);
            int thubWidth = 200;
            int thubHeight = _thubWidth == 0 ? 200 : (thubWidth * _thubHeight / _thubWidth );  
            
            string thubImagePath = path +  "files\\" + name + ".jpg";
            string command = string.Format("\"{0}\" -i \"{1}\" -ss {2} -vframes 1 -r 1 -ac 1 -ab 2 -s {3}*{4} -f image2 \"{5}\"", ffmpegPath, oriVideoPath, frameIndex, thubWidth, thubHeight, thubImagePath);
            Cmd.RunCmd(command);
            return name;
        }

        /// <summary>
        /// 獲取視頻的幀寬度和幀高度
        /// </summary>
        /// <param name="videoFilePath">mov文件的路徑</param>
        /// <returns>null表示獲取寬度或高度失敗</returns>
        public static void GetMovWidthAndHeight(string videoFilePath, out int width, out int height)
        {
            try
            {

                //執行命令獲取該文件的一些信息 
                string ffmpegPath = AppDomain.CurrentDomain.BaseDirectory +  "/ffmpeg.exe";
                string output;
                string error;
                ExecuteCommand("\"" + ffmpegPath + "\"" + " -i " + "\"" + videoFilePath + "\"", out output, out error);
                if (string.IsNullOrEmpty(error))
                {
                    width = 0;
                    height = 0;
                }

                //通過正則表達式獲取信息裡面的寬度信息
                Regex regex = new Regex("(\\d{2,4})x(\\d{2,4})", RegexOptions.Compiled);
                Match m = regex.Match(error);
                if (m.Success)
                {
                    width = int.Parse(m.Groups[1].Value);
                    height = int.Parse(m.Groups[2].Value);
                }
                else
                {
                    width = 0;
                    height = 0;
                }
            }
            catch (Exception)
            {
                width = 0;
                height = 0;
            }
        }

        public static void ExecuteCommand(string command, out string output, out string error)
        {
            try
            {
                //創建一個進程
                Process pc = new Process();
                pc.StartInfo.FileName = command;
                pc.StartInfo.UseShellExecute = false;
                pc.StartInfo.RedirectStandardOutput = true;
                pc.StartInfo.RedirectStandardError = true;
                pc.StartInfo.CreateNoWindow = true;

                //啟動進程
                pc.Start();

                //準備讀出輸出流和錯誤流
                string outputData = string.Empty;
                string errorData = string.Empty;
                pc.BeginOutputReadLine();
                pc.BeginErrorReadLine();

                pc.OutputDataReceived += (ss, ee) =>
                {
                    outputData += ee.Data;
                };

                pc.ErrorDataReceived += (ss, ee) =>
                {
                    errorData += ee.Data;
                };

                //等待退出
                pc.WaitForExit();

                //關閉進程
                pc.Close();

                //返迴流結果
                output = outputData;
                error = errorData;
            }
            catch (Exception)
            {
                output = null;
                error = null;
            }
        }

    }

    public class Util
    {
        public static string GetRandomLetterAndNumberString(Random random, int length)
        {
            if (length < 0)
            {
                throw new ArgumentOutOfRangeException("length");
            }
            char[] pattern = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
            string result = "";
            int n = pattern.Length;
            for (int i = 0; i < length; i++)
            {
                int rnd = random.Next(0, n);
                result += pattern[rnd];
            }
            return result;
        }
    }

    class Cmd
    {
        private static string CmdPath = @"C:\Windows\System32\cmd.exe";
        /// <summary>
        /// 執行cmd命令 返回cmd視窗顯示的信息
        /// 多命令請使用批處理命令連接符:
        /// <![CDATA[
        /// &:同時執行兩個命令
        /// |:將上一個命令的輸出,作為下一個命令的輸入
        /// &&:當&&前的命令成功時,才執行&&後的命令
        /// ||:當||前的命令失敗時,才執行||後的命令]]>
        /// </summary>
        /// <param name="cmd">執行的命令</param>
        public static string RunCmd(string cmd)
        {
            cmd = cmd.Trim().TrimEnd('&') + "&exit";//說明:不管命令是否成功均執行exit命令,否則當調用ReadToEnd()方法時,會處於假死狀態
            using (Process p = new Process())
            {
                p.StartInfo.FileName = CmdPath;
                p.StartInfo.UseShellExecute = false;        //是否使用操作系統shell啟動
                p.StartInfo.RedirectStandardInput = true;   //接受來自調用程式的輸入信息
                p.StartInfo.RedirectStandardOutput = true;  //由調用程式獲取輸出信息
                p.StartInfo.RedirectStandardError = true;   //重定向標準錯誤輸出
                p.StartInfo.CreateNoWindow = true;          //不顯示程式視窗
                p.Start();//啟動程式

                //向cmd視窗寫入命令
                p.StandardInput.WriteLine(cmd);
                p.StandardInput.AutoFlush = true;

                //獲取cmd視窗的輸出信息
                string output = p.StandardOutput.ReadToEnd();
                p.WaitForExit();//等待程式執行完退出進程
                p.Close();

                return output;
            }
        }
    }
}

我還是都寫到一塊了,大家記得分開!

SController.cs  這個是針對手機端單獨拎出來的,裡面不需要什麼內容

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SignalR.Controllers
{
    public class SController : Controller
    {
        // GET: S
        public ActionResult Index()
        {
            return View();
        }
    }
}

 根目錄新建一個ViewModels文件夾,裡面新建FileModel.cs文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SignalR.ViewModels
{
    public class FileModel
    {
        /// <summary>
        /// 1 : 圖片  2:視頻
        /// </summary>
        public int t { get; set; }

        public string url { get; set; }
    }
} 

RedisHelper.cs

using Microsoft.AspNet.SignalR.Messaging;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks;
using System.Web;

namespace SignalR
{
    public class RedisHelper
    {
        private static string Constr = "xxxx.cn:6379";

        private static object _locker = new Object();
        private static ConnectionMultiplexer _instance = null;

        /// <summary>
        /// 使用一個靜態屬性來返回已連接的實例,如下列中所示。這樣,一旦 ConnectionMultiplexer 斷開連接,便可以初始化新的連接實例。
        /// </summary>
        public static ConnectionMultiplexer Instance
        {
            get
            {
                if (Constr.Length == 0)
                {
                    throw new Exception("連接字元串未設置!");
                }
                if (_instance == null)
                {
                    lock (_locker)
                    {
                        if (_instance == null || !_instance.IsConnected)
                        {
                            _instance = ConnectionMultiplexer.Connect(Constr);
                        }
                    }
                }
                //註冊如下事件
                _instance.ConnectionFailed += MuxerConnectionFailed;
                _instance.ConnectionRestored += MuxerConnectionRestored;
                _instance.ErrorMessage += MuxerErrorMessage;
                _instance.ConfigurationChanged += MuxerConfigurationChanged;
                _instance.HashSlotMoved += MuxerHashSlotMoved;
                _instance.InternalError += MuxerInternalError;
                return _instance;
            }
        }

        static RedisHelper()
        {
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static IDatabase GetDatabase()
        {
            return Instance.GetDatabase();
        }

        /// <summary>
        /// 這裡的 MergeKey 用來拼接 Key 的首碼,具體不同的業務模塊使用不同的首碼。
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string MergeKey(string key)
        {
            return "SignalR:"+ key;
            //return BaseSystemInfo.SystemCode + key;
        }

        /// <summary>
        /// 根據key獲取緩存對象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T Get<T>(string key)
        {
            key = MergeKey(key);
            return Deserialize<T>(GetDatabase().StringGet(key));
        }

        /// <summary>
        /// 根據key獲取緩存對象
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static object Get(string key)
        {
            key = MergeKey(key);
            return Deserialize<object>(GetDatabase().StringGet(key));
        }

        /// <summary>
        /// 設置緩存
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireMinutes"></param>
        public static void Set(string key, object value, int expireMinutes = 0)
        {
            key = MergeKey(key);
            if (expireMinutes > 0)
            {
                GetDatabase().StringSet(key, Serialize(value), TimeSpan.FromMinutes(expireMinutes));
            }
            else
            {
                GetDatabase().StringSet(key, Serialize(value));
            }

        }


        /// <summary>
        /// 判斷在緩存中是否存在該key的緩存數據
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool Exists(string key)
        {
            key = MergeKey(key);
            return GetDatabase().KeyExists(key); //可直接調用
        }

        /// <summary>
        /// 移除指定key的緩存
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool Remove(string key)
        {
            key = MergeKey(key);
            return GetDatabase().KeyDelete(key);
        }

        /// <summary>
        /// 非同步設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public static async Task SetAsync(string key, object value)
        {
            key = MergeKey(key);
            await GetDatabase().StringSetAsync(key, Serialize(value));
        }

        /// <summary>
        /// 根據key獲取緩存對象
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static async Task<object> GetAsync(string key)
        {
            key = MergeKey(key);
            object value = await GetDatabase().StringGetAsync(key);
            return value;
        }

        /// <summary>
        /// 實現遞增
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static long Increment(string key)
        {
            key = MergeKey(key);
            //三種命令模式
            //Sync,同步模式會直接阻塞調用者,但是顯然不會阻塞其他線程。
            //Async,非同步模式直接走的是Task模型。
            //Fire - and - Forget,就是發送命令,然後完全不關心最終什麼時候完成命令操作。
            //即發即棄:通過配置 CommandFlags 來實現即發即棄功能,在該實例中該方法會立即返回,如果是string則返回null 如果是int則返回0.這個操作將會繼續在後臺運行,一個典型的用法頁面計數器的實現:
            return GetDatabase().StringIncrement(key, flags: CommandFlags.FireAndForget);
        }

        /// <summary>
        /// 實現遞減
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static long Decrement(string key, string value)
        {
            key = MergeKey(key);
            return GetDatabase().HashDecrement(key, value, flags: CommandFlags.FireAndForget);
        }

        /// <summary>
        /// 序列化對象
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        private static byte[] Serialize(object o)
        {
            if (o == null)
            {
                return null;
            }
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            using (MemoryStream memoryStream = new MemoryStream())
            {
                binaryFormatter.Serialize(memoryStream, o);
                byte[] objectDataAsStream = memoryStream.ToArray();
                return objectDataAsStream;
            }
        }

        /// <summary>
        /// 反序列化對象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream"></param>
        /// <returns></returns>
        private static T Deserialize<T>(byte[] stream)
        {
            if (stream == null)
            {
                return default(T);
            }
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            using (MemoryStream memoryStream = new MemoryStream(stream))
            {
                T result = (T)binaryFormatter.Deserialize(memoryStream);
                return result;
            }
        }

        /// <summary>
        /// 配置更改時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerConfigurationChanged(object sender, EndPointEventArgs e)
        {
            //LogHelper.SafeLogMessage("Configuration changed: " + e.EndPoint);
        }

        /// <summary>
        /// 發生錯誤時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerErrorMessage(object sender, RedisErrorEventArgs e)
        {
            //LogHelper.SafeLogMessage("ErrorMessage: " + e.Message);
        }

        /// <summary>
        /// 重新建立連接之前的錯誤
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerConnectionRestored(object sender, ConnectionFailedEventArgs e)
        {
            //LogHelper.SafeLogMessage("ConnectionRestored: " + e.EndPoint);
        }

        /// <summary>
        /// 連接失敗 , 如果重新連接成功你將不會收到這個通知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerConnectionFailed(object sender, ConnectionFailedEventArgs e)
        {
            //LogHelper.SafeLogMessage("重新連接:Endpoint failed: " + e.EndPoint + ", " + e.FailureType +(e.Exception == null ? "" : (", " + e.Exception.Message)));
        }

        /// <summary>
        /// 更改集群
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerHashSlotMoved(object sender, HashSlotMovedEventArgs e)
        {
            //LogHelper.SafeLogMessage("HashSlotMoved:NewEndPoint" + e.NewEndPoint + ", OldEndPoint" + e.OldEndPoint);
        }

        /// <summary>
        /// redis類庫錯誤
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MuxerInternalError(object sender, InternalErrorEventArgs e)
        {
            //LogHelper.SafeLogMessage("InternalError:Message" + e.Exception.Message);
        }

        //場景不一樣,選擇的模式便會不一樣,大家可以按照自己系統架構情況合理選擇長連接還是Lazy。
        //建立連接後,通過調用ConnectionMultiplexer.GetDatabase 方法返回對 Redis Cache 資料庫的引用。從 GetDatabase 方法返回的對象是一個輕量級直通對象,不需要進行存儲。

        /// <summary>
        /// 使用的是Lazy,在真正需要連接時創建連接。
        /// 延遲載入技術
        /// 微軟azure中的配置 連接模板
        /// </summary>
        //private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
        //{
        //    //var options = ConfigurationOptions.Parse(constr);
        //    ////options.ClientName = GetAppName(); // only known at runtime
        //    //options.AllowAdmin = true;
        //    //return ConnectionMultiplexer.Connect(options);
        //    ConnectionMultiplexer muxer = ConnectionMultiplexer.Connect(Coonstr);
        //    muxer.ConnectionFailed += MuxerConnectionFailed;
        //    muxer.ConnectionRestored += MuxerConnectionRestored;
        //    muxer.ErrorMessage += MuxerErrorMessage;
        //    muxer.ConfigurationChanged += MuxerConfigurationChanged;
        //    muxer.HashSlotMoved += MuxerHashSlotMoved;
        //    muxer.InternalError += MuxerInternalError;
        //    return muxer;
        //});


        #region  當作消息代理中間件使用 一般使用更專業的消息隊列來處理這種業務場景

        /// <summary>
        /// 當作消息代理中間件使用
        /// 消息組建中,重要的概念便是生產者,消費者,消息中間件。
        /// </summary>
        /// <param name="channel"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public static long Publish(string channel, string message)
        {
            StackExchange.Redis.ISubscriber sub = Instance.GetSubscriber();
            //return sub.Publish("messages", "hello");
            return sub.Publish(channel, message);
        }

        /// <summary>
        /// 在消費者端得到該消息並輸出
        /// </summary>
        /// <param name="channelFrom"></param>
        /// <returns></returns>
        public static void Subscribe(string channelFrom)
        {
            StackExchange.Redis.ISubscriber sub = Instance.GetSubscriber();
            sub.Subscribe(channelFrom, (channel, message) =>
            {
                Console.WriteLine((string)message);
            });
        }

        #endregion

        /// <summary>
        /// GetServer方法會接收一個EndPoint類或者一個唯一標識一臺伺服器的鍵值對
        /// 有時候需要為單個伺服器指定特定的命令
        /// 使用IServer可以使用所有的shell命令,比如:
        /// DateTime lastSave = server.LastSave();
        /// ClientInfo[] clients = server.ClientList();
        /// 如果報錯在連接字元串後加 ,allowAdmin=true;
        /// </summary>
        /// <returns></returns>
        public static IServer GetServer(string host, int port)
        {
            IServer server = Instance.GetServer(host, port);
            return server;
        }

        /// <summary>
        /// 獲取全部終結點
        /// </summary>
        /// <returns></returns>
        public static EndPoint[] GetEndPoints()
        {
            EndPoint[] endpoints = Instance.GetEndPoints();
            return endpoints;
        }
    }
}

  

 總體項目結構是這樣的

 

 

下期我將把前端代碼列出來,這個我只是為了實現功能,大神勿噴

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 基本包裝類型 普通變數不能直接調用屬性或者方法 對象可以直接調用屬性和方法 基本包裝類型: 本身是基本類型, 但是在執行代碼的過程中, 如果這種類型的變數調用了屬性或者是方法, 那麼這種類型就不再是基本類型了, 而是基本包裝類型, 這個變數也不是普通的變數了, 而是基本包裝類型對象 string n ...
  • css 層疊樣式表,作用是為標簽加效果 基本選擇器 元素選擇器 標簽名稱{css屬性:值} id選擇器 id{} 類選擇器 .class1{屬性:值} 屬性選擇器 後代選擇器 html代碼: 組合選擇器 div,a{ color: yellow;} css樣式引入方式 head標簽中引入 外部文件引 ...
  • Array對象的方法 Array.isArray(對象) >判斷這個對象是不是數組 instanceof關鍵字 判斷對象是不是數組類型:兩種方法: //1 instanceof var obj = []; console.log(obj instanceof Array);//ture //2 使用 ...
  • 練習1: var str = "我的寶寶最可愛,聲音嗲嗲的"; var key = "可愛"; //先獲取要截取的字元串的索引位置 var index = str.indexOf(key); //從指定的桅子花開始截取,截取一定個數 str = str.substr(index, 7); conso ...
  • switch功能:簡單條件判斷,比if判斷標簽少些不等於相同功能,視個人習慣而用。 ...
  • 實例方法 >必須要通過new的方式創建的對象(實例對象)來調用的方法 靜態方法 >直接通過大寫的構造函數的名字調用的方法(直接通過大寫的對象名字調用的) 字元串的常用屬性: .length >字元串的長度 var str = "12345"; console.log(str.length); .ch ...
  • 1、是否合法IP地址 export function validateIP(rule, value,callback) { if(value==''||value==undefined||value==null){ callback(); }else { const reg = /^(\d{1,2} ...
  • 導言 在一個風和日麗的一天,看完了瘋狂HTML 5+CSS 3+JavaScript講義,跟著做了書里最後一章的俄羅斯方塊小游戲,並做了一些改進,作為自己前端學習的第一站。 游戲效果: 製作思路 因為書里的俄羅斯方塊比較普通,太常規了,不是很好看,所以我在網上找了上面那張圖片,打算照著它來做。(請無 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...