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".htm"" />
<!--日志文件名-->
<param name="RollingStyle" value="Date" />
<!--文件创建的方式,这里是以Date方式创建-->
<!--错误日志布局-->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="<HR COLOR=red>%n异常时间:%d [%t] <BR>%n异常级别:%-5p <BR>%n异 常 类:%c [%x] <BR>%n%m <BR>%n <HR Size=1>" />
</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".htm"" />
<param name="RollingStyle" value="Date" />
<!--信息日志布局-->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="<HR COLOR=blue>%n日志时间:%d [%t] <BR>%n日志级别:%-5p <BR>%n日 志 类:%c [%x] <BR>%n%m <BR>%n <HR Size=1>" />
</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