实现一个监控 IP 的 windows 服务

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现一个监控 IP 的 windows 服务相关的知识,希望对你有一定的参考价值。

实现一个监控 IP 的 windows 服务

Intro

我们公司的 VPN 用自己的电脑连公司的台式机的时候需要用 IP 地址,有一次尝试去连的时候发现连不上,第二天到公司发现 IP 变掉了,不是之前连的 IP 了,于是就想写一个简单 Windows 服务来监控台式机的 IP 变化

Overview

在 C# 里我们可以使用 Dns.GetHostAddresses() 方法来获取 IP 地址,我们可以每隔一段时间就判断一下当前的 IP 地址,为了方便测试,可以把这个时间定义在配置里,这样本地开发的时候比较方便

为了避免消息太多,我们可以做一个简单的检查,如果 IP 地址不变,就不发消息了,只有当 IP 信息变化的时候再发消息

我们办公使用的是 Google Chat, 所以打算使用 Google Chat 来发消息,也可以根据需要改成自己想用的通知方式

Implement

首先我们可以新建一个 worker 服务,使用 dotnet cli 新建即可

dotnet new worker -n IpMonitor

如果不习惯没有解决方案文件,也可以新建一个解决方案文件并将项目添加到解决方案文件中

cd IpMonitor
dotnet new sln
dotnet sln add ./IpMonitor.csproj

然后我们来改造我们的 Worker, Worker 其实就是一个后台服务,我们的服务比较简单就直接在上面改了

public sealed class Worker : BackgroundService

    private readonly TimeSpan _period;
    private readonly INotification _notification;
    private readonly ILogger<Worker> _logger;

    private volatile string _previousIpInfo = string.Empty;

    public Worker(IConfiguration configuration, INotification notification, ILogger<Worker> logger)
    
        _notification = notification;
        _logger = logger;
        _period = configuration.GetAppSetting<TimeSpan>("MonitorPeriod");
        if (_period <= TimeSpan.Zero)
        
            _period = TimeSpan.FromMinutes(10);
        
    

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    
        using var timer = new PeriodicTimer(_period);
        while (await timer.WaitForNextTickAsync(stoppingToken))
        
            try
            
                var host = Dns.GetHostName();
                var ips = await Dns.GetHostAddressesAsync(host, stoppingToken);
                var ipInfo = $"Environment.MachineName - host\\n ips.Order(new IpAddressComparer()).Select(x => x.MapToIPv4().ToString()).StringJoin(", ")";
                if (_previousIpInfo == ipInfo)
                
                    _logger.LogDebug("IpInfo not changed");
                    continue;
                

                _logger.LogInformation("Ip info: IpInfo", ipInfo);
                await _notification.SendNotification(ipInfo);
                _previousIpInfo = ipInfo;
            
            catch (Exception e)
            
                _logger.LogError(e, "GetIp exception");
            
        
    

这里我们使用了 .NET 6 引入的 PeriodicTimer 来实现定时任务,自定义了一个 IpAddressComparer 来对 IP 地址做一个排序,实现如下:

public sealed class IpAddressComparer: IComparer<IPAddress>

    public int Compare(IPAddress? x, IPAddress? y)
    
        if (ReferenceEquals(x, y)) return 0;
        if (ReferenceEquals(null, y)) return 1;
        if (ReferenceEquals(null, x)) return -1;

        var bytes1 = x.MapToIPv4().ToString().SplitArray<byte>(new [] '.' );
        var bytes2 = y.MapToIPv4().ToString().SplitArray<byte>(new [] '.' );
        for (var i = 0; i < bytes1.Length; i++)
        
            if (bytes1[i] != bytes2[i])
            
                return bytes1[i].CompareTo(bytes2[i]);
            
        
        
        return 0;
    

通知使用了 Google Chat 的 webhook API,可以自定义一个 Space,添加一个 webhook 即可,添加成功即可获取一个 webhook URL, 发送消息 API 可以参考文档:https://developers.google.com/chat/api/guides/message-formats/basic

实现如下:

public sealed class GoogleChatNotification: INotification

    private readonly HttpClient _httpClient;
    private readonly string _webhookUrl;

    public GoogleChatNotification(HttpClient httpClient, IConfiguration configuration)
    
        _httpClient = httpClient;
        _webhookUrl = Guard.NotNullOrEmpty(configuration.GetAppSetting("GChatWebHookUrl"));
    
    
    public async Task<bool> SendNotification(string text)
    
        using var response = await _httpClient.PostAsJsonAsync(_webhookUrl, new  text );
        return response.IsSuccessStatusCode;
    

Program 文件中注册我们新加的服务就可以了

然后我们进行一些改造来发布和部署 Windows 服务,可以按照文档的提示将项目发布为单文件,部署我比较喜欢 powershell,写了两个简单的 powershell script 来安装和卸载 Windows 服务

首先我们可以在项目里添加 Microsoft.Extensions.Hosting.WindowsServices 的引用,并添加一些发布属性

<PropertyGroup>
    <PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PlatformTarget>x64</PlatformTarget>
</PropertyGroup>

Program 中注册 windows 服务相关配置

using IpMonitor;

Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    
        services.AddHostedService<Worker>();
        services.AddSingleton<HttpClient>();
        services.AddSingleton<INotification, GoogleChatNotification>();
    )
#if !DEBUG
    // https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service
    .UseWindowsService(options =>
    
        options.ServiceName = "IpMonitor";
    )
#endif
    .Build()
    .Run();

安装服务 powershell 脚本:

$serviceName = "IpMonitor"
Write-Output "serviceName: $serviceName"

dotnet publish -c Release -o out
$destDir = Resolve-Path ".\\out"
$ipMonitorPath = "$destDir\\IpMonitor.exe"

Write-Output "Installing service... $ipMonitorPath $destDir"
New-Service $serviceName -BinaryPathName $ipMonitorPath
Start-Service $serviceName
Write-Output "Service $serviceName started"

卸载服务 powershell 脚本:

$serviceName = "IpMonitor"
Stop-Service $serviceName
Write-Output "Service $serviceName stopped"
Remove-Service $serviceName
Write-Output "Service $serviceName removed"

运行效果如下(脚本运行需要以管理员权限运行):

我们可以使用 Get-Service IpMonitor 来查看服务状态

install

也可以在任务管理器和服务中查看

最后再把我们的服务卸载掉

uninstall

More

发布为 Windows 服务时如果有问题可以通过 event log 来排查,在 event log 里可以看到我们服务的日志

References

  • https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service

  • https://github.com/WeihanLi/SamplesInPractice/tree/master/IpMonitor

  • https://developers.google.com/chat/api/guides/message-formats/basic

以上是关于实现一个监控 IP 的 windows 服务的主要内容,如果未能解决你的问题,请参考以下文章

实践:DNS域名轮循业务监控

如何安全的实现远程监控机房里的温度?

cacti监控windows简单笔记!

如何计算字体大小以适应单文本行和多行弹性项目上的文本?

zabbix监控安装及实现对tomcat服务器监控

怎样在Windows系统中安装SNMP来监控服务器