SignalR
ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程。实时 Web 功能是指这样一种功能:当所连接的客户端变得可用时服务器代码可以立即向其推送内容,而不是让服务器等待客户端请求新的数据。
服务端:控制台应用程序----可以看到客户端连接掉线的消息,给那个客户端发送了消息
客户端:windows服务---通过命令注册到windows服务里开机自动启动用到Topshelf、定时调度Quartz,同时客户端又是一个服务端来给UDP客户端发送消息。
本来想实现Quartz的job调度,但是这样注入不到windows服务里,最后还是选择了用 System.Timers.Timer
客户端实现如下:
1、新建控制台程序起名叫SignalRClient
2、引入如下nuget包
Microsoft.AspNet.SignalR.Client
log4net
Quartz
Topshelf
3、普通的控制台程序是无法注入到windows服务里,必须用Topshelfc才可以注入到windows服务里,并且可以启动停止。
注入的命令很简单打开dos命令
4、客户端代码如下:
新建TopshelfService类
using Microsoft.AspNet.SignalR.Client;
using SignalRClient.Entity;
using System;
using System.Configuration;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace SignalRClient
{
public class TopshelfService
{
private static Socket udpServer;
private static string _message = "";
static HubConnection connection = null;
static IHubProxy myHub = null;
static string url = ConfigurationManager.AppSettings.Get("url");
public void Start()
{
//服务逻辑
try
{
double time = double.Parse(ConfigurationManager.AppSettings["timeout"]);
System.Timers.Timer t = new System.Timers.Timer(time);//实例化Timer类,设置间隔时间为10000毫秒;
t.Elapsed += new System.Timers.ElapsedEventHandler(theout);//到达时间的时候执行事件;
t.AutoReset = true;//设置是执行一次(false)还是一直执行(true);
t.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件;
t.Start();
//以下是有开启了一个socket当作服务端给本地socket发送消息
udpServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//udpServer.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6678));//绑定端口号和IP
Console.WriteLine("socket服务端开启成功!");
//Thread t1 = new Thread(ReciveMsg);//开启接收消息线程
//t1.Start();
Thread t2 = new Thread(sendMsg);//开启发送消息线程
t2.Start();
Console.ReadKey();
}
catch (Exception ex)
{
LogHelper.WriteLog(ex.Message.ToString(), ex);
}
}
public void Stop()
{
}
private static void theout(object source, System.Timers.ElapsedEventArgs e)
{
NewMethod();
}
/// <summary>
/// UDP服务端向特定ip的主机的端口发送数据报
/// </summary>
static void sendMsg()
{
EndPoint point = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6677);
while (true)
{
string msg = CommonMsg.Message;
if (!string.IsNullOrWhiteSpace(msg))
{
udpServer.SendTo(Encoding.UTF8.GetBytes(msg), point);
//_message = "";
}
}
}
/// <summary>
/// 一直等待服务端推送消息
/// </summary>
private static void NewMethod()
{
string clientName = ConfigurationManager.AppSettings.Get("clientName");
Console.WriteLine("客户端: " + "设备id =》" + clientName + "开启");
connection = new HubConnection(url, "clientName=" + clientName);
//类名必须与服务端一致
myHub = connection.CreateHubProxy("BroadcastHub");
//方法名必须与服务端一致
myHub.On<string>("ReceiveMsg", ReceiveMsg);
connection.Start().ContinueWith(task =>
{
if (!task.IsFaulted)
{
Console.WriteLine("与服务器连接成功");
}
else
{
Console.WriteLine("与服务器连接失败,请确认服务器是否开启!");
}
}).Wait();
}
/// <summary>
/// 从服务端接收到的消息
/// </summary>
/// <param name="message"></param>
private static void ReceiveMsg(string message)
{
CommonMsg.Message = message;
Console.WriteLine("来自服务器的消息:" + message);
}
/// <summary>
/// 接收发送给本机ip对应端口号的数据报
/// </summary>
static void ReciveMsg()
{
while (true)
{
EndPoint point = new IPEndPoint(IPAddress.Any, 0);//用来保存发送方的ip和端口号
byte[] buffer = new byte[1024];
int length = udpServer.ReceiveFrom(buffer, ref point);//接收数据报
string message = Encoding.UTF8.GetString(buffer, 0, length);
Console.WriteLine(point.ToString() + message);
}
}
}
}
新建一个公共接收消息类CommonMsg
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignalRClient.Entity
{
public class CommonMsg
{
public static string Message = "";
}
}
新建一个日志帮助类LogHelper
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignalRClient
{
public class LogHelper
{
public static readonly log4net.ILog loginfo = log4net.LogManager.GetLogger("loginfo");
public static readonly log4net.ILog logerror = log4net.LogManager.GetLogger("logerror");
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\\SinalRClinetLog\\" />
<!--日志输出到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>
新建修改app.config文件
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="MySection" type="SignalRClient.ConfigSectionHandler, SignalRClient" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<!--每个设备的id-->
<add key="clientName" value="69" />
<!--服务器地址-->
<add key="url" value="http://IP:5000" />
<!--每1分钟重启一次客户端-->
<add key="timeout" value="60000" />
<add key="timeoutS" value="0/60 * * * * ?" />
<add key="ClientSettingsProvider.ServiceUri" value="" />
</appSettings>
<system.web>
<membership defaultProvider="ClientAuthenticationMembershipProvider">
<providers>
<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
</providers>
</membership>
<roleManager defaultProvider="ClientRoleProvider" enabled="true">
<providers>
<add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" />
</providers>
</roleManager>
</system.web>
</configuration>
修改应用程序主入口
using Microsoft.AspNet.SignalR.Client;
using SignalRClient.Entity;
using System.ComponentModel;
using System.Configuration;
using System.Net.Sockets;
using Topshelf;
namespace SignalRClient
{
public class Program
{
static void Main(string[] args)
{
HostFactory.Run(c =>
{
c.Service<TopshelfService>(s =>
{
s.ConstructUsing(b => new TopshelfService());
s.WhenStarted(o => o.Start());
s.WhenStopped(o => o.Stop());
});
c.RunAsLocalSystem();
c.StartAutomatically();
c.SetServiceName("SignalRClientServices");
c.SetDisplayName("SignalRClientServices");
c.SetDescription("SignalRClientServices");
});
}
}
}
大功告成
服务端源码地址:https://download.csdn.net/download/it_ziliang/11250515
客户端源码地址:https://download.csdn.net/download/it_ziliang/11250526