IHostedService для tcp-серверов в .NET Core

Я пытаюсь создать небольшой tcp-сервер / демон с ядром asp.net в качестве веб-интерфейса для взаимодействия с сервером. Я нашел 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 и демоны должны взаимодействовать?

Как мне взаимодействовать с самой запущенной службой из контроллера?

Можно ли использовать IHostedService для этой цели или это лучший способ полностью отделять интерфейс Asp.Net от службы / сервера, например запустив демон и asp.net как отдельные процессы с каким-то механизмом IPC?

Каким путем вы в итоге пошли для этого?

A Houghton 27.01.2021 14:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
1
7 056
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

На самом деле, хостинг-сервис пока не так силен. Поэтому люди обычно используют третий продукт. Однако можно взаимодействовать с размещенной службой и контроллером. Я буду использовать ваш код в качестве примера для достижения этих целей:

  1. TcpServer может получать две команды, чтобы мы могли переключать состояние размещенной службы с TcpClient.
  2. Контроллер WebServer может вызывать метод TcpServer косвенно (через посредника) и отображать его как html.

Соединять контроллер с размещенной службой - не лучшая идея. Чтобы вызвать метод из размещенной службы, мы можем ввести посредника. Посредник - это не более чем служба, которая служит синглтоном (потому что на нее будет ссылаться размещенная служба):

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

@agnsaft Поскольку вы не можете напрямую внедрить размещенную службу в контроллер, вам следует ввести объект, служащий посредником. IMediator - это не более чем одноэлементный сервис.

itminus 27.09.2018 11:56

К сожалению, мне не удалось заставить это работать. Не могли бы вы подробнее рассказать, как добавляется посредник и для чего нужен ExecHandler?

agnsaft 22.10.2018 23:15

@agnsaft IMediator - это не более чем простая одноэлементная служба, которая используется для взаимодействия с вашим TCP-сервером с контроллеров. Возможно, у вас может быть несколько размещенных сервисов для взаимодействия, поэтому я использую здесь шаблон наблюдателя. Все размещенные службы регистрируют обработчики событий с помощью настраиваемого мероприятиеExecHandler. Чтобы добавить посредника, просто зарегистрируйте его как одноэлементную службу: services.AddSingleton<IMediator,Mediator>().

itminus 23.10.2018 00:20

@agnsaft О, я забыл упомянуть, что ExecHandler - это простой делегат, вы можете настроить делегат по своему усмотрению. Поскольку мы ожидаем здесь что-то вроде string Exec1(string), мы можем определить ExecHandler как public delegate string ExecHandler(string status);.

itminus 23.10.2018 00:25

Мое предложение похоже на @itminus

В зависимости от желаемого сценария:

  1. Если вы хотите получить доступ к сервису ТОЛЬКО изнутри с одних и тех же контроллеров / страниц приложения:

Не создавайте TCP Listener. Используйте фоновую очередь для запросов и фоновую службу для обработки запросов, вызываемых из кода, как описано в документации.

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 полностью независима от веб-приложения, отключите службу TCP для отдельного серверного приложения. См. документы о том, как создать «чистый» сервис без служебных данных asp / kestrel в ядре dotnet 2.1.

Другие вопросы по теме