跨外网络云服务器推送消息到每个客户端ASP.NET SignalR

SignalR

ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程。实时 Web 功能是指这样一种功能:当所连接的客户端变得可用时服务器代码可以立即向其推送内容,而不是让服务器等待客户端请求新的数据。

服务端:控制台应用程序----可以看到客户端连接掉线的消息,给那个客户端发送了消息

客户端:windows服务---通过命令注册到windows服务里开机自动启动用到Topshelf、定时调度Quartz,同时客户端又是一个服务端来给UDP客户端发送消息。

废话不多说直接上代码

服务端实现如下:

1、新建控制台程序起名叫SignalRServer

2、引入如下nuget包

Microsoft.AspNet.SignalR
Microsoft.Owin
Microsoft.Owin.Cors
Microsoft.Owin.Host.HttpListener
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Hosting
Microsoft.Owin.Security
Topshelf
log4net

 新建一个集线器类BroadcastHub继承Hub

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

namespace SignalRServer.Entity
{
    public class BroadcastHub : Hub
    {
        private readonly BroadcastContext broadcastContext;
        public List<string> clientNames = new List<string>();
        public BroadcastHub()
        {
            broadcastContext = BroadcastContext.Instance;
        }

        /// <summary>
        /// 重写连接事件
        /// </summary>
        /// <returns></returns>
        public override Task OnConnected()
        {
            //http://localhost:1025/signalr/start?clientProtocol=2.1&transport=webSockets&connectionData=[%7B%22Name%22:%22BroadcastHub%22%7D]&connectionToken=AQAAANCMnd8BFdERjHoAwE%2FCl%2BsBAAAAXuODpT5BCkiFAeRnu6KQGAAAAAACAAAAAAAQZgAAAAEAACAAAABL%2Bx9m6PIYP7fywds%2BvYNZf93gFbIeayz3qP9VGoOQhwAAAAAOgAAAAAIAACAAAABuWj%2F%2FqzXpcnhuxsK8JHjIy3I7e7b5BxSgmDnnb263vTAAAACyf0G%2BPfivCw2YD6bWoaUYzYAU0OWyg4I%2FQyR1aWOPpv87GBHAoMzpYO3%2BDlkF6xxAAAAAiwLJIGVbN5O8zUekVOn9DkaI2OuQIdTrnWIxI%2BTqt7LZh7QvnUdZ3CEkH2fmmpiz60L7%2BMQsLkWKzIyCmMg%2BtA%3D%3D&clientName=1C1B0D29912A
            try
            {
                //通过url地址截取识别那个客户端
                string[] urlstr = Context.Request.Url.Query.Split('&');
                string clientNamedic = urlstr[4].Split('=')[1];

                if (!CommonUser.clientNames.ContainsKey(clientNamedic))
                {
                    //给客户端起名识别客户端然后把clientid保存起来
                    Console.WriteLine("设备id为 " + clientNamedic + " 连接成功!");
                    CommonUser.clientNames.Add(clientNamedic, Context.ConnectionId);
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog(ex.Message.ToString(), ex);
            } 
            return base.OnConnected();
        }

        public override Task OnDisconnected(bool stopCalled)
        {
            try
            {
                string ConnectionIdstr = Context.ConnectionId;
                if (CommonUser.clientNames.ContainsValue(ConnectionIdstr))
                {
                    var firstKey = CommonUser.clientNames.FirstOrDefault(q => q.Value == ConnectionIdstr).Key;
                    CommonUser.clientNames.Remove(firstKey);
                    Console.WriteLine("设备id为 " + firstKey + " 已离线!");
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog(ex.Message.ToString(), ex);
            }
           
            return base.OnDisconnected(stopCalled);
        }
    }
}

新建一个广播类BroadcastContext

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

namespace SignalRServer.Entity
{
    public class BroadcastContext
    {
        //单例模式 所有客户端都共用此实例 才能达到广播的效果
        private static readonly BroadcastContext instance =
        new BroadcastContext(GlobalHost.ConnectionManager.GetHubContext<BroadcastHub>());

        private readonly IHubContext context;
        //功能业务类
        private readonly FunctionApp functionApp;

        public static BroadcastContext Instance
        {
            get { return instance; }
        }

        private BroadcastContext(IHubContext context)
        {
            this.context = context;
            functionApp = new FunctionApp(context);
            functionApp.Action();
        }
    }
}

新建一个上下文类FunctionApp

using Microsoft.AspNet.SignalR;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Script.Serialization;

namespace SignalRServer.Entity
{
    public class FunctionApp
    {
        //上下文
        private readonly IHubContext context;
        static object locker = new object(); // !!
        private static CancellationTokenSource cts = new CancellationTokenSource();
        public FunctionApp(IHubContext context)
        {
            this.context = context;

        }

        public void Action()
        {
            //测试广播功能
            Task.Run(() => Start());
        }

        private void Start()
        {
            try
            {
                string url = ConfigurationManager.AppSettings["dataurl"];
                var token = cts.Token;
                while (true)
                {
                    try

                    {
                        //控制并发用的Parallel
                        Parallel.ForEach(CommonUser.clientNames, new ParallelOptions { MaxDegreeOfParallelism = 10, CancellationToken = token }, client =>
                        {
                            //foreach (var item in CommonUser.clientNames)
                            //{
                            //客户端调用的方法名必须一致
                            //读取数据的地方,我是读取接口数据
                            string response = HttpUtils.Get(url + client.Key);
                            JavaScriptSerializer js = new JavaScriptSerializer();
                            var req = js.Deserialize<JsonResultData>(response.ToString());
                            if (req.errorcode != 2)
                            {
                                if (CommonUser.clientNames.ContainsKey(client.Key))
                                {
                                    string json = JsonConvert.SerializeObject(req.data);
                                    Console.WriteLine("向设备id为" + client.Key + " 发送=》" + json);
                                    context.Clients.Client(CommonUser.clientNames[client.Key]).ReceiveMsg(json);
                                }

                            }
                            //if (CommonUser.clientNames.ContainsKey("1C1B0D29912A"))
                            //{
                            //    context.Clients.Client(CommonUser.clientNames["1C1B0D29912A"]).ReceiveMsg("11122");
                            //}

                            // }
                            Thread.Sleep(1000);
                        });
                    }
                    catch (Exception ex)
                    {
                        LogHelper.WriteLog(ex.Message.ToString(), ex);
                    }
                   
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog(ex.Message.ToString(), ex);
            }
        }
    }
    public class JsonResultData
    {
        /// <summary>
        /// 返回编码0执行成功/数据上传成功1执行失败100请求的服务不存在200数据格式错误
        /// </summary>
        public int errorcode { set; get; }
        /// <summary>
        /// 提示信息
        /// </summary>
        public string message { set; get; }
        /// <summary>
        /// 附加信息
        /// </summary>
        public object data { set; get; }

    }
}

新建一个用来存取客户端的内存类CommonUser

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SignalRServer.Entity
{
    public class CommonUser
    {
        public static Dictionary<string, string> clientNames = new Dictionary<string, string>();
    }
}

新建一个http获取接口数据的get和post公共类

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace SignalRServer.Entity
{
    //获取第三方api的工具类
    public class HttpUtils
    {
        public static string Get(string Url)
        {
            //System.GC.Collect();
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);
            request.Proxy = null;
            request.KeepAlive = false;
            request.Method = "GET";
            request.ContentType = "application/json; charset=UTF-8";
            request.AutomaticDecompression = DecompressionMethods.GZip;

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream myResponseStream = response.GetResponseStream();
            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8);
            string retString = myStreamReader.ReadToEnd();

            myStreamReader.Close();
            myResponseStream.Close();

            if (response != null)
            {
                response.Close();
            }
            if (request != null)
            {
                request.Abort();
            }

            return retString;
        }

        public static string Post(string Url, string Data, string Referer)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);
            request.Method = "POST";
            request.Referer = Referer;
            byte[] bytes = Encoding.UTF8.GetBytes(Data);
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = bytes.Length;
            Stream myResponseStream = request.GetRequestStream();
            myResponseStream.Write(bytes, 0, bytes.Length);

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            StreamReader myStreamReader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
            string retString = myStreamReader.ReadToEnd();

            myStreamReader.Close();
            myResponseStream.Close();

            if (response != null)
            {
                response.Close();
            }
            if (request != null)
            {
                request.Abort();
            }
            return retString;
        }
    }
}

新建一个Startup类

using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Owin.Cors;
namespace SignalRServer
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);
            app.MapSignalR();
        }
    }
}

新建一个服务类为了能运用topshelf注入到windows服务里SignalService

using Microsoft.Owin.Hosting;
using System;
using System.Configuration;
namespace SignalRServer.Entity
{
    public class SignalService
    {
        static string url = ConfigurationManager.AppSettings["url"];
        public void Start()
        {
            //业务处理
            WebApp.Start<Startup>(url);
            Console.WriteLine("远程服务启动成功!");
            Console.ReadKey();
        }

        public void Stop()
        {

        }
    }
}

新建一个lognet日志类LogHelper

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SignalRServer
{
    public class LogHelper
    {
        public static readonly log4net.ILog loginfo = log4net.LogManager.GetLogger("ServerInfo");
        public static readonly log4net.ILog logerror = log4net.LogManager.GetLogger("ServerError");
        public static void WriteLog(string info)
        {
            if (loginfo.IsInfoEnabled)
            {
                loginfo.Info(info);
            }
        }

        public static void WriteLog(string info, Exception ex)
        {
            if (logerror.IsErrorEnabled)
            {
                logerror.Error(info, ex);
            }
        }
    }
}

新建一个日志config

<?xml version="1.0" encoding="utf-8"?>
<log4net>
  <!--错误日志类-->
  <logger name="logerror">
    <!--日志类的名字-->
    <level value="ALL" />
    <!--定义记录的日志级别-->
    <appender-ref ref="ErrorAppender" />
    <!--记录到哪个介质中去-->
  </logger>
  <!--信息日志类-->
  <logger name="loginfo">
    <level value="ALL" />
    <appender-ref ref="InfoAppender" />
  </logger>
  <!--错误日志附加介质-->
  <appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender">
    <!-- name属性指定其名称,type则是log4net.Appender命名空间的一个类的名称,意思是,指定使用哪种介质-->
    <param name="File" value="D:\\Log\\SinalRServerLog\\" />
    <!--日志输出到exe程序这个相对目录下-->
    <param name="AppendToFile" value="true" />
    <!--输出的日志不会覆盖以前的信息-->
    <param name="MaxSizeRollBackups" value="100" />
    <!--备份文件的个数-->
    <param name="MaxFileSize" value="10240" />
    <!--当个日志文件的最大大小-->
    <param name="StaticLogFileName" value="false" />
    <!--是否使用静态文件名-->
    <param name="DatePattern" value="yyyyMMdd&quot;.htm&quot;" />
    <!--日志文件名-->
    <param name="RollingStyle" value="Date" />
    <!--文件创建的方式,这里是以Date方式创建-->
    <!--错误日志布局-->
    <layout type="log4net.Layout.PatternLayout">
      <param name="ConversionPattern" value="&lt;HR COLOR=red&gt;%n异常时间:%d [%t] &lt;BR&gt;%n异常级别:%-5p &lt;BR&gt;%n异 常 类:%c [%x] &lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;"  />
    </layout>
  </appender>
  <!--信息日志附加介质-->
  <appender name="InfoAppender" type="log4net.Appender.RollingFileAppender">
    <param name="File" value="Log\\LogInfo\\" />
    <param name="AppendToFile" value="true" />
    <param name="MaxFileSize" value="10240" />
    <param name="MaxSizeRollBackups" value="100" />
    <param name="StaticLogFileName" value="false" />
    <param name="DatePattern" value="yyyyMMdd&quot;.htm&quot;" />
    <param name="RollingStyle" value="Date" />
    <!--信息日志布局-->
    <layout type="log4net.Layout.PatternLayout">
      <param name="ConversionPattern" value="&lt;HR COLOR=blue&gt;%n日志时间:%d [%t] &lt;BR&gt;%n日志级别:%-5p &lt;BR&gt;%n日 志 类:%c [%x] &lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;"  />
    </layout>
  </appender>
</log4net>

修改应用程序的入口Program文件

using SignalRServer.Entity;
using Topshelf;

namespace SignalRServer
{
    class Program
    {
        static void Main(string[] args)
        {
            HostFactory.Run(c =>
            {
                c.SetServiceName("SignalRServerServices");
                c.SetDisplayName("SignalRServerServices");
                c.SetDescription("SignalRServerServices");

                c.Service<SignalService>(s =>
                {
                    s.ConstructUsing(b => new SignalService());
                    s.WhenStarted(o => o.Start());
                    s.WhenStopped(o => o.Stop());
                });
            });
        }
    }
}

修改app.config文件

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <appSettings>
    <add key="dataurl" value="" />
    <!--阿里云一定要用*来代替ip地址-->
    <add key="url" value="http://*:5000" />
  </appSettings>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

服务端源码地址:https://download.csdn.net/download/it_ziliang/11250515

客户端源码地址:https://download.csdn.net/download/it_ziliang/11250526

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值