简体   繁体   中英

IHostedService for tcp servers in .NET Core

I am trying to build a small tcp server/daemon with asp.net core as a web frontend to interact with the server. I have found IHostedService/BackgroundService which seems to provide a low effort alternative to bundle the server and the frontend together.

The code looks basically like this at the moment (echo server for testing purposes):

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);
            }
        }
    }
}

And is initialized in Startup.cs like this:

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

Is there a common pattern for how modern Asp.Net core applications and daemons should cooperate?

How would I interact with the running service itself from a Controller?

Is IHostedService even usable for this purpose or is it a better way that fully decouples the Asp.Net frontend and the service/server, eg by running the daemon and asp.net as seperate processes with some sort of IPC mechanism?

Is there a common pattern for how modern Asp.Net core applications and daemons should cooperate?

Actually , the hosted service is not that powerful for the present . So people usually use a third product . However , it's possible to communicate with hosted service and controller . I'll use your code as an example to achieve these goals :

  1. The TcpServer is able to receive two commands so that we can switch the state of hosted service from a TcpClient .
  2. The controller of WebServer can invoke method of TcpServer indirectly (through a mediator ), and render it as html

在此处输入图片说明

It's not a good idea to couple controller with hosted service . To invoke method from hosted service , we can introduce a Mediator . A mediator is no more than a service that serves as a singleton (because it will referenced by hosted service) :

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();
    }
}

A Hosted Service needs to realize the existence of IMediator and expose his method to IMediator in some way :

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)
        {
            // ...
        }
    }
}

To allow control the status from the NetCat TcpServer , I make it able to receive two commands from clients to switch the state of background service :

    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();
            }
        }
    }

If you want to invoke the method of background service within a controller, simply inject the 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();
    }
}

My suggestion is similar to @itminus

Depending on your desired scenario:

  1. If you want to access the service ONLY internally from the same app controllers/pages:

Do not create TCP Listener. Use the background queue for requests and background service for processing requests, invoked from the code as explained in the docs

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

  1. If you want to access the service both via TCP from other servers/clients etc AND internally from the hosting aspcore app:

Implement the separate processing service (logic server) as in point 1. You can inject it and invoke from both your TCP listener background service and controllers.

  1. Of course you can access your own service via HttpClient from the same app, but it would seem strange to use the whole TCP stack for internal calls.

  2. If the TCP processing is totally independent from the web application, then cut the TCP service out to separate server application. See docs on how to create "pure" service without asp/kestrel overhead in dotnet core 2.1.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM