深度优先

这个家伙好懒,除了文章什么都没留下

0%

Nuget 安装 NLog.Extensions.Loggin

项目目录下新建 nlog.config,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="logfile" xsi:type="File" fileName="logs/${shortdate}.log" />
<target name="database" xsi:type="Database" dbProvider="MySql.Data.MySqlClient.MySqlConnection, MySql.Data">
<connectionString>${var:connectionString}</connectionString>
<commandText>
insert into `nlog` (
`application`, `logged`, `level`, `message`,
`callsite`, `exception`,`stacktrace`
) values (
@application, @logged, @level, @message,
@callsite, @exception,@stacktrace
);
</commandText>
<parameter name="@application" layout="iPaaS_Auth_API" />
<parameter name="@logged" layout="${date}" />
<parameter name="@level" layout="${level}" />
<parameter name="@message" layout="${message}" />
<parameter name="@callSite" layout="${callsite:filename=true}" />
<parameter name="@exception" layout="${exception:tostring}" />
<parameter name="@stacktrace" layout="${stacktrace}" />
</target>
</targets>
<rules>
<logger name="*" minlevel="Error" writeTo="logfile" />
<logger name="*" minlevel="Error" writeTo="database" />
</rules>
</nlog>

右键设置文件属性:

在 Startup.cs 的 Configure 中添加:

1
2
3
4
5
6
7
8
#region Nlog记日志

loggerFactory.AddNLog();
NLog.LogManager.LoadConfiguration("nlog.config").GetCurrentClassLogger();
NLog.LogManager.Configuration.Variables["connectionString"] = Configuration.GetConnectionString("DefaultConnection");
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); //避免日志中的中文输出乱码

#endregion Nlog记日志

在 appsetting.json 中配置数据库连接字符串:

1
2
3
4
5
6
7
8
9
10
11
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "server=***;uid=***;pwd=Htm4XW2rjEDJYD3y;port=3306;database=***;TreatTinyAsBoolean=false;Connection Timeout=180"
}
}

在数据库中创建nlog表:

1
2
3
4
5
6
7
8
9
10
11
12
13
DROP TABLE IF EXISTS `nlog`;

CREATE TABLE `nlog` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`application` varchar(50) DEFAULT NULL,
`logged` datetime(6) NOT NULL,
`level` varchar(50) DEFAULT NULL,
`message` varchar(2000) DEFAULT NULL,
`callsite` varchar(512) DEFAULT NULL,
`exception` longtext,
`stacktrace` longtext,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

还需要 Nuget 安装 MySql.Data

使用一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Program
{
private static Logger logger = LogManager.GetCurrentClassLogger();

public static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
var configuration = builder.Build();

NLog.LogManager.LoadConfiguration("nlog.config").GetCurrentClassLogger();
NLog.LogManager.Configuration.Variables["connectionString"] = configuration.GetConnectionString("DefaultConnection");
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); //避免日志中的中文输出乱码

//最常见的记录信息,一般用于普通输出
logger.Trace("输出一条记录信息成功!");
//同样是记录信息,不过出现的频率要比Trace少一些,一般用来调试程序
logger.Debug("输出一条Debug信息成功!");
//信息类型的消息
logger.Info("输出一条消息类型信息成功!");
//警告信息,一般用于比较重要的场合
logger.Warn("输出一条警告信息成功");
//错误信息
logger.Error("输出一条错误信息成功!");
//致命异常信息。一般来讲,发生致命异常之后程序将无法继续执行。
logger.Fatal("输出一条致命信息成功!");
//CreateWebHostBuilder(args).Build().Run();
}
}

logs文件

数据库

树莓派4B 采集传感器的温湿度 通过.Net Core 3.1的控制台程序将数据上传到服务器端,程序部署在docker中

客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
using Iot.Device.DHTxx;
using Microsoft.AspNetCore.SignalR.Client;
using NLog;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace IotClient
{
class Program
{
private static HubConnection _connection;
private static readonly Logger logger = LogManager.GetLogger("IotClient");
private static Guid DeviceGuid = Guid.Parse("886dae3f-1e08-41ce-9531-e2d326ee693b");
static async Task Main(string[] args)
{
logger.Debug("启动客户端");

_connection = new HubConnectionBuilder()
.WithUrl("http://ip:port/iotHub")
.WithAutomaticReconnect()
.Build();

_connection.On<string>("IotClientRsult", message => IotClientRsult(message));

try
{
await _connection.StartAsync();
logger.Info($"SignalR连接成功!");
}
catch (Exception ex)
{
logger.Fatal($"SignalR连接失败:{ex.Message}");
}
using Dht11 dht = new Dht11(26);

List<Attribute> attributes = new List<Attribute>()
{
new Attribute(){Identifier ="humidity", Data =new List<Data>()},
new Attribute(){Identifier ="temperature", Data =new List<Data>()},
};
UploadData uploadData = new UploadData()
{
DeviceGuid = DeviceGuid,
Attributes = attributes,
Timestamp = DateTimeHelper.GetTimeStamp()
};
while (true)
{
try
{
var humidityOrNan = dht.Humidity.ToString("0.0");
var temperatureOrNan = dht.Temperature.Celsius.ToString("0.0");

if (dht.IsLastReadSuccessful)
{
var temperature = decimal.Parse(temperatureOrNan);
var humidity = decimal.Parse(humidityOrNan);

Console.WriteLine($"Temperature: {temperature} °C, Humidity: { humidity} % ");
if (_connection.State == HubConnectionState.Connected)
{
try
{
uploadData.Timestamp = DateTimeHelper.GetTimeStamp();
uploadData.Attributes[0].Data.Clear();
uploadData.Attributes[0].Data.Add(new Data
{
Value = humidity,
Timestamp = DateTimeHelper.GetTimeStamp(),
});
uploadData.Attributes[1].Data.Clear();
uploadData.Attributes[1].Data.Add(new Data
{
Value = temperature,
Timestamp = DateTimeHelper.GetTimeStamp(),
});
await _connection.InvokeAsync("IotClientUpload", uploadData);
}
catch (Exception ex)
{
logger.Fatal($"SignalR发送数据失败:{ex.Message}");
}
}
}
}
catch (Exception ex)
{
logger.Fatal($"出现错误:{ex.Message} {ex.StackTrace}");
}
await Task.Delay(1000 * 5);
}
}

private static void IotClientRsult(string message)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}{message}");
}
}
}

数据对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UploadData
{
public List<Attribute> Attributes { get; set; }
public int Timestamp { get; set; }
public Guid DeviceGuid { get; set; }
}

public class Attribute
{
public string Identifier { get; set; }
public List<Data> Data { get; set; }
}

public class Data
{
public decimal Value { get; set; }
public int Timestamp { get; set; }
}

服务器端,SignalR:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using demo.Database;
using demo.Entities;
using demo.SignalR.IotClient;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace demo.SignalR
{
public class IotHub: Hub
{
private readonly MyContext _myContext;
private readonly ILoggerFactory _loggerFactory;
public IotHub(MyContext myContext,
ILoggerFactory loggerFactory)
{
_myContext = myContext;
_loggerFactory = loggerFactory;
}
public async Task IotClientUpload(UploadData uploadData)
{
var logger = _loggerFactory.CreateLogger(typeof(IotHub));
logger.LogError(Newtonsoft.Json.JsonConvert.SerializeObject(uploadData));
var connectionId = Context.ConnectionId;
if (uploadData is null)
{
await IotClientRsult(connectionId, $"上传内容为空!");
return;
}
if (uploadData.Attributes != null)
{
foreach (var attribute in uploadData.Attributes)
{
foreach (var data in attribute.Data)
{
_myContext.IotData.Add(new IotData()
{
Guid = Guid.NewGuid(),
Timestamp = data.Timestamp,
Value = data.Value,
Identifier = attribute.Identifier,
DeviceGuid = uploadData.DeviceGuid
});
}
}
}
int count = await _myContext.SaveChangesAsync();

await IotClientRsult(connectionId, $"成功上传 {count} 条数据!");
}
public async Task IotClientRsult(string connectionId, string message)
{
await Clients.Client(connectionId).SendAsync("IotClientRsult", message);
}
}
}

Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim-arm32v7 AS base
WORKDIR /app

ENV LANG C.UTF-8

# 时区设置
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

ENTRYPOINT ["dotnet", "IotClient.dll"]

Build:

1
2
3
docker build -t iot-client-image .

docker run -d --name iot-client -v /opt/netcore/IotClient:/app --privileged iot-client-image

Docker访问Raspberry Pi GPIO引脚,需要加 –privileged 参数

效果图:


后续再根据数据做异常预警

树莓派 安装 .Net Core 3.1:

1
2
3
4
5
6
7
8
9
wget https://download.visualstudio.microsoft.com/download/pr/8c839c0e-a5ae-4254-8d8b-c012528fe601/c147e26bad68f97eacc287a71e01331d/aspnetcore-runtime-3.1.0-linux-arm.tar.gz

wget https://download.visualstudio.microsoft.com/download/pr/67766a96-eb8c-4cd2-bca4-ea63d2cc115c/7bf13840aa2ed88793b7315d5e0d74e6/dotnet-sdk-3.1.100-linux-arm.tar.gz

mkdir -p $HOME/pi/dotnet && tar zxf dotnet-sdk-3.1.100-linux-arm.tar.gz -C $HOME/pi/dotnet
tar zxf aspnetcore-runtime-3.1.0-linux-arm.tar.gz -C $HOME/pi/dotnet

export DOTNET_ROOT=$HOME/pi/dotnet
export PATH=$PATH:$HOME/pi/dotnet

临时变量永久生效:

1
vi /etc/profile

添加:

1
2
export DOTNET_ROOT=$HOME/pi/dotnet 
export PATH=$PATH:$HOME/pi/dotnet

frp下载:https://github.com/fatedier/frp/releases

服务器端配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[common]
bind_port = 17000
vhost_http_port = 8080
#vhost_https_port = 8080


# dashboard admin
dashboard_port = 端口
dashboard_user = 账号
dashboard_pwd = 密码

privilege_token = sgdf@dsfd.df
#authentication_timeout = 60
privilege_allow_ports = 10000-20000

max_pool_count = 10
subdomain_host = 你的域名.com
log_file = ./frps.log
log_level = info
log_max_days = 7

服务端配置账号:

1
2
3
4
5
6
[common]
# token 和客户端配置的 accesstoken保持一致,不一致无法保持连接
token=raspberrypi*密码
# 服务截止日期
expirydate=2020-10-01

客户端配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
[common]
server_addr = 公网ip
server_port = 17000

privilege_token = 对应服务器的token
pool_count = 2
log_file = ./frpc.log
log_level = info
log_max_days = 10

#访问用户,与服务端用户配置文件名一致
user=raspberrypi
# 与服务端用户配置文件中的token保持一致
accesstoken=raspberrypi****

# [项目名称],保持唯一,其余参考官方配置文件
# [pi]
# type = http
# local_port = 80
# 域名前缀为xx,结合服务端的域名,完整的网址为xx.***.***.com
# subdomain = pi
# use_encryption = true
# use_compression = true

[ssh]
type = tcp
local_port = 22
remote_port = 10022
use_encryption = true
use_compression = true

[web1]
type = tcp
local_port = 80
local_ip = 127.0.0.1
remote_port = 10080

[web2]
type = tcp
local_port = 8080
local_ip = 127.0.0.1
remote_port = 18080

[mysql]
type = tcp
local_port = 3306
local_ip = 127.0.0.1
remote_port = 13306

[redis]
type = tcp
local_port = 6379
local_ip = 127.0.0.1
remote_port = 16379

使用 Systemd 设置 frp 开机启动:

Systemd 配置文件在 /lib/systemd/system/ 目录下,不同类型的文件使用不同的后缀,如: .service .socket .timer

常用命令如下:

1
2
3
4
5
6
7
8
9
10
11
systemctl status frps.service       # 查看 frps 服务状态
systemctl cat frps.service # 查看 frps 服务配置
sudo systemctl start frps.service # 启动 frps 服务
sudo systemctl stop frps.service # 停止 frps 服务
sudo systemctl daemon-reload # 重新加载配置,修改 systemd 配置执行
sudo systemctl enable frps.service # 设置开机启动,根据 install 建立软链
sudo systemctl disable frps.service # 取消开机启动,根据 install 移除软链

man systemd.directives # 查看帮助
man systemd.service
man systemd.unit

frps Systemd 配置文件

创建 /lib/systemd/system/frpc.service 文件,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=frps server daemon
Documentation=https://github.com/fatedier/frp
After=network-online.target

[Service]
ExecStart=/usr/local/bin/frps -c /usr/local/etc/frps.ini
Type=simple
User=nobody
Group=nogroup
WorkingDirectory=/tmp
Restart=on-failure
RestartSec=60s

[Install]
WantedBy=multi-user.target
  • [Unit]
  • After 在网络就绪后启动服务,关于网络就绪 NetworkTarget
  • [Service]
  • ExecStart 启动时执行的命令
  • Type simple 执行 ExecStart 指定的命令,启动主进程,还有很多其他类别
  • User 执行命令的用户为 nobody
  • Group 执行命令的组为 nogroup
  • WorkingDirectory 命令的工作目录
  • Restart 失败后尝试重启,失败是根据退出状态码进行判断的
  • RestartSec 失败 60s 后尝试重启
  • [Install]
  • WantedBy 执行 systemctl enable frps 命令,会在 /etc/systemd/system/multi-user.target.wants/frps.service 目录下创建一个软链 添加或修改配置后需要执行 sudo systemctl daemon-reload 重新加载配置,执行 sudo systemctl enable frps.service 之后会开机启动。

fprc 配置文件

创建 /lib/systemd/system/frpc.service 文件,配置和 frps 类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=frpc server daemon
Documentation=https://github.com/fatedier/frp
After=network-online.target

[Service]
ExecStart=/usr/local/bin/frpc -c /usr/local/etc/frpc.ini
Type=simple
User=nobody
Group=nogroup
WorkingDirectory=/tmp
Restart=on-failure
RestartSec=60s

[Install]
WantedBy=multi-user.target

添加或修改配置后需要执行 sudo systemctl daemon-reload 重新加载配置,执行 sudo systemctl enable frpc.service 之后会开机启动。

frp后台运行和停止

centos
1.运行

1
nohup ./frps -c frps.ini >/dev/null 2>&1 &

或者客户端:

1
2
nohup ./frpc -c ./frpc.ini >/dev/null 2>&1 &

2.停止
先找到这个进程

1
2
3
ps -aux|grep frp| grep -v grep
root 3600 0.1 0.1 110188 9484 pts/0 Sl 15:04 0:00 ./frpc -c ./frpc.ini

执行之后如果显示这样则成功了

然后kill -9 进程号

1
kill -9 3600

转自:https://notfound.cn/posts/systemd-frp/

转自:https://segmentfault.com/a/1190000017911165