我的.NET Core学习实践:开源日志库与服务端框架分享

56 阅读5分钟

在学习.NET Core的过程中,我通过实际项目练习各种开发技巧,沉淀了两个开源项目。经过多个版本的迭代,我自认为这些代码已达到可在小型项目中使用的程度。诚邀开发者朋友们体验指正,您的反馈将帮助这些项目变得更好,让我们共同进步。

开源日志记录库: Logging

安装教程

在 Nuget 中搜索 Nobita.Logging,安装对应的包。

包名版本状态
Nobita.Logging.Core2.0.0正常
Nobita.Logging.Console2.0.0正常
Nobita.Logging.File2.0.0正常
Nobita.Logging.Parallel2.0.0正常
Nobita.Logging.MySql1.3.0已放弃更新
Nobita.Logging.Proxying1.3.0已放弃更新

使用说明

  • 控制台日志记录
// 创建日志记录器
var logger = new ConsoleLogger(Console.Out);

// 记录日志
logger.Information("Test","Hello World!");

  • 文件日志记录
// 创建文件管理器
// 目前支持日期文件管理器和默认文件管理器
// 参数:日志文件管理路径
var manager = new DateFileManager("./Logs");

// 创建日志记录器
var logger = new FileLogger(manager);

// 记录日志
logger.Information("Test","Hello World!");

  • 并行日志处理
// 假设要同时使用控制台和文件日志记录器
var consoleLogger = new ConsoleLogger(Console.Out);
var fileLogger = new FileLogger(new DateFileManager("./Logs"));

// 创建调度器
// 顺序调度器会利用线程池链式调用,会确保日志存储与输出的顺序一致
// 并行调度器会利用线程池并行调用,可能会导致日志存储与输出的顺序不一致
// 一般推荐使用顺序调度器,可以确保日志存储与输出的顺序一致
var dispatcherManager = new DispatcherManager
{
    new OrderedDispatcher(consoleLogger),
    new OrderedDispatcher(fileLogger)
};

// 创建假日志记录器
// 假日志器不会实质性的记录日志,只是将日志记录到调度器中,并确保日志的顺序一致。
var fakeLogger = new FakeLogger(dispatchManager);

// 记录日志
fakeLogger.Information("Test","Hello World!");

  • 配合依赖注入
// 创建容器
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<Logger>(_ =>
{
    var manager = new DispatcherManager
    {
        new OrderedDispatcher(new ConsoleLogger(Console.Out)),
        new OrderedDispatcher(new FileLogger(new DateFileManager("Logs")))
    };
    return new FakeLogger(manager);
});

// 创建服务提供程序
var serviceProvider = serviceCollection.BuildServiceProvider();
var logger = serviceProvider.GetService<Logger>();

// 记录日志
logger.Information("Test",Hello World!");

开源服务端框架: ServerFramework

安装教程

在 NuGet 中搜索 'Nobita.ServerFramework' 安装相关的包。

包名称版本状态
Nobita.ServerFramework.Core1.0.0正常
Nobita.ServerFramework.Network1.0.0正常
Nobita.ServerFramework.Timer1.0.0正常
Nobita.ServerFramework.Cluster1.0.0正常
Nobita.ServerFramework.Delegates1.0.0正常

使用说明

  • Attribute 说明
Attribute位置作用
[Abstract]Nobita.ServerFramework.Core标记抽象服务,在自动反射时忽略
[Singleton]Nobita.ServerFramework.Core标记单例服务,在自动反射时注册单例
[Name]Nobita.ServerFramework.Core命名服务,在自动反射时注册命名服务
[Dependency]Nobita.ServerFramework.Core设置依赖的服务,确定服务启动和关闭的先后顺序
[InstanceCount]Nobita.ServerFramework.Core设置实例数量,用于控制服务实例的数量
[Bind]Nobita.ServerFramework.Network硬编码绑定IP地址和端口
[BindJson]Nobita.ServerFramework.Network通过配置文件绑定IP地址和端口
[Backlog]Nobita.ServerFramework.Network设置TCP连接的缓冲区大小
[BufferSize]Nobita.ServerFramework.Network设置UDP数据包的缓冲区大小
[SocketInfo]Nobita.ServerFramework.Network设置Socket的信息
[Delegate]Nobita.ServerFramework.Delegates设置委托
[DelegateFixture]Nobita.ServerFramework.Delegates设置委托的容器类
[Period]Nobita.ServerFramework.Timer设置定时任务执行周期(毫秒)
[Strict]Nobita.ServerFramework.Timer严格模式,任务启动后会暂停定时器,任务结束后恢复(默认为false)
[Parallel]Nobita.ServerFramework.Cluster设置集群最大和最小的处理节点数(推荐优先使用线程池,除非要保证先后顺序)
  • BindJson配置文件格式

本框架采用约定大于配置的设计思路,所有使用了BindJsonAttribute的服务会自动按照 'services:serviceName' 路径去搜索。

{
  "services": {
    "tcpserviceimplement": {
      "address": "0.0.0.0",
      "port": 8080
    },
    "udpserviceimplement": {
      "address": "0.0.0.0",
      "port": 8082
    }
  }
}

  • 创建TCP服务
[Singleton]
[Name(nameof(TcpServiceImplement))]
[BindJson("application.json")]
[Backlog(100)]
[Dependency(typeof(UdpServiceImplement))]
public sealed class TcpServiceImplement : TcpService
{
    protected override Task OnProcessAsync(TcpContext context, CancellationToken token)
    {
        throw new NotImplementedException();
    }
}
  • 创建UDP服务
[Singleton]
[Name(nameof(UdpServiceImplement))]
[BindJson("application.json")]
[BufferSize(1024)]
public sealed class UdpServiceImplement : UdpService
{
    protected override Task OnProcessAsync(UdpContext context, CancellationToken token)
    {
        throw new NotImplementedException();
    }
}
  • 创建定时器服务
[Singleton]
[Strict]
[Period(1000)]
[Name(nameof(TimerImplement))]
[Dependency(typeof(TcpServiceImplement))]
[Dependency(typeof(UdpServiceImplement))]
public sealed class TimerImplement(IServiceProvider provider) : TimerService
{
    private IServiceProvider Provider { get; } = provider;
    protected override void OnTimer(object? state)
    {
        Console.WriteLine("Provider: " + Provider);
    }
}
  • 创建集群服务
[Singleton]
[Name(nameof(TestCluster))]
[Parallel(1, 2)]
private class TestCluster : ClusterMaster
{
    public TestCluster()
    {
        OnIncreased += (sender, args) =>
        {
            Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} Increased: " + args.CurrentCount);
        };
        OnDecreased += (sender, args) =>
        {
            Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} Decreased: " + args.CurrentCount);
        };
    }

    protected override void OnAction(object? param, CancellationToken token)
    {
        throw new NotImplementedException();
    }
}
  • 启动服务程序
// 创建服务端
var app = new ServerApplication();

// 配置服务端
app.MapDependencies()
   .MapServices()
   .UsePassiveMode();

// 运行服务端
app.Run();

特点

  • 空依赖检测
private void CheckEmptyWaiting()
{
    // 检测等待队列中是否存在空等待现象
    // 即某服务依赖了某服务,但该服务未在等待队列或者运行队列中
    // 因为依赖的服务只能是单例,所以检测类型就行
    if (Waiting.Any(x => x.Dependencies.Count > 0 &&
                         x.Dependencies.Any(dependency =>
                             Waiting.Any(y => y.GetType() == dependency) == false &&
                             Running.Any(y => y.GetType() == dependency) == false)))
        throw new ArgumentException("Empty waiting detected.");
}
  • 循环依赖检测
// 核心拓扑排序
...

// 检测是否存在循环依赖
// 如果结果的长度与入度表长度不一致,则说明存在循环依赖
if (result.Count != graph.Count) 
   throw new ArgumentException("Circular dependency detected.");
  • 群组委托

在本框架中,设计了一套便于维护与扩展的委托反射机制。通过 DelegateFixture 类定义委托容器,并借助群组(Group)配置,可将分散在多个容器中的委托进行统一聚合与管理。

每个容器可根据需要进行独立初始化,系统在运行时自动完成委托的注册与调用,实现了行为的灵活组织与动态执行。

[DelegateFixture("Analyze")]
public class UserHandlers
{
    public class UserHandlers()
    {
        // 进行相关的初始化
    }

    [Delegate("Login")]
    public async Task LoginHandler(object? param, CancellationToken token)
    {
        throw new NotImplementedException();
    }

    [Delegate("Register")]
    public async Task RegisterHandler(object? param, CancellationToken token)
    {
        throw new NotImplementedException();
    }

    [Delegate("Unregister")]
    public async Task UnregisterHandler(object? param, CancellationToken token)
    {
        throw new NotImplementedException();
    }
}

[DelegateFixture("Analyze")]
public class OtherHandlers
{
    public OtherHandlers()
    {
        // 进行相关的初始化
    }
    
    [Delegate("OtherProtocol")]
    public async Task OtherHandler(object? param, CancellationToken token)
    {
        throw new NotImplementedException();
    }
}

public class Program 
{
    public static void Main(string[] args)
    {
        // 创建委托注册表
        var registry = DelegateHelper.CreateRegistry("Analyze");
    
        // 触发委托
        await registry.InvokeAsync("Login", null, CancellationToken.None);
    }
}