9

我正在尝试使用 asp.net 核心构建一个小型 tcp 服务器/守护进程作为 Web 前端来与服务器交互。我发现 IHostedService/BackgroundService 似乎提供了一种将服​​务器和前端捆绑在一起的低成本替代方案。

目前代码看起来基本上是这样的(用于测试目的的回显服务器):

public class Netcat : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        TcpListener listener = new TcpListener(IPAddress.Any, 8899);
        listener.Start();
        while(!stoppingToken.IsCancellationRequested)
        {
            TcpClient client = await listener.AcceptTcpClientAsync();
            NetworkStream stream = client.GetStream();

            while (!stoppingToken.IsCancellationRequested)
            {
                byte[] data = new byte[1024];
                int read = await stream.ReadAsync(data, 0, 1024, stoppingToken);

                await stream.WriteAsync(data, 0, read, stoppingToken);
            }
        }
    }
}

并在 Startup.cs 中初始化,如下所示:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHostedService<Netcat>();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

现代 Asp.Net 核心应用程序和守护进程应该如何合作有一个共同的模式吗?

我将如何从 Controller 与正在运行的服务本身进行交互?

IHostedService 是否甚至可用于此目的,或者它是完全分离 Asp.Net 前端和服务/服务器的更好方法,例如通过使用某种 IPC 机制将守护进程和 asp.net 作为单独的进程运行?

4

2 回答 2

8

现代 Asp.Net 核心应用程序和守护进程应该如何合作有一个共同的模式吗?

实际上,托管服务目前还没有那么强大。所以人们通常使用第三种产品。但是,可以与托管服务和控制器进行通信。我将以您的代码为例来实现这些目标:

  1. 能够接收两个命令,TcpServer以便我们可以从TcpClient.
  2. 控制器WebServer可以间接调用方法TcpServer(通过中介),并将其呈现为 html

在此处输入图像描述

将控制器与托管服务耦合不是一个好主意。要从托管服务调用方法,我们可以引入一个 Mediator。中介者不过是作为单例的服务(因为它将被托管服务引用):

public interface IMediator{
    event ExecHandler ExecHandler ; 
    string Exec1(string status);
    string Exec2(int status);
    // ...
}

public class Mediator: IMediator{

    public event ExecHandler ExecHandler ;
    public string Exec1(string status)
    {
        if(this.ExecHandler==null) 
            return null;
        return this.ExecHandler(status);
    }

    public string Exec2(int status)
    {
        throw new System.NotImplementedException();
    }
}

托管服务需要以某种方式实现其方法的存在IMediator并公开:IMediator

public class Netcat : BackgroundService
{
    private IMediator Mediator ;
    public Netcat(IMediator mediator){
        this.Mediator=mediator;
    }

    // method that you want to be invoke from somewhere else
    public string Hello(string status){
        return $"{status}:returned from service";
    }

    // method required by `BackgroundService`
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        TcpListener listener = new TcpListener(IPAddress.Any, 8899);
        listener.Start();
        while(!stoppingToken.IsCancellationRequested)
        {
            // ...
        }
    }
}

为了允许从 NetCat 控制状态TcpServer,我让它能够从客户端接收两个命令来切换后台服务的状态:

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        TcpListener listener = new TcpListener(IPAddress.Any, 8899);
        listener.Start();
        while(!stoppingToken.IsCancellationRequested)
        {
            TcpClient client = await listener.AcceptTcpClientAsync();
            Console.WriteLine("a new client connected");
            NetworkStream stream = client.GetStream();

            while (!stoppingToken.IsCancellationRequested)
            {
                byte[] data = new byte[1024];
                int read = await stream.ReadAsync(data, 0, 1024, stoppingToken);
                var cmd= Encoding.UTF8.GetString(data,0,read);
                Console.WriteLine($"[+] received : {cmd}");

                if(cmd=="attach") { 
                    this.Mediator.ExecHandler+=this.Hello;
                    Console.WriteLine($"[-] exec : attached");
                    continue;
                }
                if(cmd=="detach") {
                    Console.WriteLine($"[-] exec : detached");
                    this.Mediator.ExecHandler-=this.Hello;
                    continue;
                }

                await stream.WriteAsync(data, 0, read, stoppingToken);
                stream.Flush();
            }
        }
    }

如果要在控制器中调用后台服务的方法,只需注入IMediator

public class HomeController : Controller
{
    private IMediator Mediator{ get; }

    public HomeController(IMediator mediator){
        this.Mediator= mediator;
    }

    public IActionResult About()
    {
        ViewData["Message"] = this.Mediator.Exec1("hello world from controller")??"nothing from hosted service";

        return View();
    }
}
于 2018-09-27T09:49:26.820 回答
0

我的建议类似于@itminus

根据您想要的场景:

  1. 如果您只想从相同的应用程序控制器/页面内部访问该服务:

不要创建 TCP 侦听器。使用后台队列处理请求,使用后台服务处理请求,从文档中解释的代码调用

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#queued-background-tasks

  1. 如果您想通过 TCP 从其他服务器/客户端等和内部托管 aspcore 应用程序访问服务:

实现第 1 点中的单独处理服务(逻辑服务器)。您可以注入它并从 TCP 侦听器后台服务和控制器调用。

  1. 当然,您可以通过HttpClient同一个应用程序访问您自己的服务,但是将整个 TCP 堆栈用于内部调用似乎很奇怪。

  2. 如果 TCP 处理完全独立于 Web 应用程序,则将 TCP 服务切出到单独的服务器应用程序中。请参阅有关如何在 dotnet core 2.1 中创建没有 asp/kestrel 开销的“纯”服务的文档。

于 2018-10-24T09:51:45.200 回答