Связь между службой Topshelf (действующей в качестве TCP-сервера) и собственным веб-интерфейсом OWIN

У меня есть служба Windows Topshelf, которая действует как сервер TCP. Внутри этого сервиса у меня также есть собственный (OWIN) WebAPI.

Моя цель - как-то разрешить WebAPI взаимодействовать с TCP-сервером, который находится и работает в одной и той же службе. Естественно, я мог бы просто использовать что-то вроде "триггерного" файла или разделяемой БД, которую можно было бы часто опрашивать, хотя я хотел бы знать о любых более оптимальных / нативных способах достижения этого.

Чтобы получить лучшее представление о проекте, представьте, что одностраничное приложение использует мой API и выполняет определенные вызовы с произвольными строковыми параметрами. Затем эти данные должны быть переданы клиентам (консольные приложения C++, использующие winsock), которые подключены к работающему TCP-серверу.

Следующий контейнер создается и передается в Topshelf HostConfigurator

class ContainerService
{
    private APIService _apiService;
    private EngineService _engineService;
    protected IDisposable WebAppHolder { get; set; }

    public bool Start(HostControl hostControl)
    {
        var host = hostControl;
        _apiService = new APIService();
        _engineService = new EngineService();

        // Initialize API service
        if (WebAppHolder == null)
        {
            WebAppHolder = _apiService.Initialize();
        }

        // Initialize Engine service
        _engineService.Initialize();

        return true;
    }

    public bool Stop(HostControl hostControl)
    {
        // Stop API service
        if (WebAppHolder != null)
        {
            WebAppHolder.Dispose();
            WebAppHolder = null;
        }

        // Stop Engine service
        _engineService.Stop();

        return true;
    }
}

Стандартные вещи Topshelf в точке входа в программу (как упомянуто выше):

HostFactory.Run(hostConfigurator =>
{
      hostConfigurator.Service<ContainerService>(containerService =>
      {
           containerService.WhenStarted((service, control) => service.Start(control));
           containerService.WhenStopped((service, control) => service.Stop(control));
      });

      hostConfigurator.RunAsLocalSystem();
      hostConfigurator.SetServiceName("Educe Service Host");
      hostConfigurator.SetDisplayName("Communication Service");
      hostConfigurator.SetDescription("Responsible for API and Engine services");
});

TCP-сервер:

public void Initialize()
{
     _serverListener = new TcpListener(new IPEndPoint(hostAddress, (int)port));
     _serverListener.Start();

     _threadDoBeginAcceptTcpClient = new Thread(() => DoBeginAcceptTcpClient(_serverListener));
     _threadDoBeginAcceptTcpClient.Start();
}

...

    public void DoBeginAcceptTcpClient(TcpListener listener)
    {
        while(!_breakThread)
        { 
            // Set the event to nonsignaled state.
            TcpClientConnected.Reset();

            // Start to listen for connections from a client.
            Console.WriteLine("Waiting for a connection...");

            // Accept the connection. 
            listener.BeginAcceptTcpClient(DoAcceptTcpClientCallback, listener);

            // Wait until a connection is made and processed before continuing.
            TcpClientConnected.WaitOne();
        }
    }

    // Process the client connection.
    public void DoAcceptTcpClientCallback(IAsyncResult ar)
    {
        // Get the listener that handles the client request.
        TcpListener listener = (TcpListener)ar.AsyncState;

        // End the operation and display the received data on the console.
        Console.WriteLine("Client connection completed");
        Clients.Add(listener.EndAcceptTcpClient(ar));

        // Signal the calling thread to continue.
        TcpClientConnected.Set();
    }

Контроллер WebAPI:

public class ValuesController : ApiController
{
     // GET api/values/5
     public string Get(int id)
     {
          return $"Foo: {id}";
     }
}

Как упоминалось ранее, я ищу "связь" между WebAPI и службой Windows. Как передать параметр "id" из вызова WebAPI в объект _engineService в моей службе Windows? Возможно, что-то похожее на MVFM Light Messenger от WPF? Идея состоит в том, что он будет затем проанализирован и отправлен соответствующему TcpClient, который хранится в списке клиентов.

Любые советы о том, как этого добиться, будут оценены. Пожалуйста, не стесняйтесь просить разъяснения / больше кода.

3 ответа

Я предполагаю, что вы пытаетесь принять параметр идентификатора запроса HTTP GET и отправить его клиентам TCP, которые подключены к EngineService. Если ваш EngineService инициализируется до вашего ApiService, я думаю, что это вопрос того, как получить дескриптор единственного экземпляра EngineService из экземпляров контроллера ApiService.

Если я следую за вами, вы можете сделать EngineService открытым статическим свойством вашего ContainerService и ссылаться на него как ContainerService.EngineService из контроллера (или в любом месте приложения) или лучше зарегистрировать свой EngineService в качестве одиночного в контейнере DI и вставить его в ApiService.

Решение (вызовы WebAPI запускают EngineService)

Теперь я использую RabbitMQ/EasyNetQ для обеспечения связи между WebApi и объектом EngineService, содержащим мои клиенты TCP.

Я случайно разделил их на две отдельные службы Projects/Topshelf.

Ниже приведен новый компонент "Communication", который создается в конструкторе EngineService.

public class Communication
{
    private readonly Logger _logger;
    private readonly IBus _bus;

    public delegate void ReceivedEventHandler(string data);
    public event ReceivedEventHandler Received;

    protected virtual void OnReceive(string data)
    {
        Received?.Invoke(data);
    }

    public Communication()
    {
        _logger = new Logger();
        _bus = RabbitHutch.CreateBus("host=localhost", reg => reg.Register<IEasyNetQLogger>(log => _logger));
        SubscribeAllQueues();
    }

    private void SubscribeAllQueues()
    {
        _bus.Receive<Message>("pipeline", message =>
        {
            OnReceive(message.Body);
        });
    }

    public void SubscribeQueue(string queueName)
    {
        _bus.Receive<Message>(queueName, message =>
        {
            OnReceive(message.Body);
        });
    }
}

Затем добавляется обработчик событий. Это означает, что как только сообщение поступит на шину, данные будут переданы обработчику событий, который впоследствии передаст их первому подключенному TCP-клиенту в списке.

public void Handler(string data)
{
    //Console.WriteLine(data);
    _clients[0].Client.Send(Encoding.UTF8.GetBytes(data));
}

...

_comPipe.Received += Handler;

И, наконец, на контроллере WebApi:

public string Get(int id)
{
    ServiceCom.SendMessage("ID: " + id);
    return "value";
}

Класс ServiceCom. Позволяет отправлять строковое сообщение на шине.

public static class ServiceCom
{
    public static void SendMessage(string messageBody)
    {
        var messageBus = RabbitHutch.CreateBus("host=localhost");
        messageBus.Send("pipeline", new Message { Body = messageBody });
    }
}

Теперь, когда это сделано, я сейчас ищу способ подключения подключенных клиентов TCP для запуска обновлений / событий в дополнительном проекте SPA, который будет действовать как приложение управления порталом / клиентом.

Мой подход, вероятно, будет использовать KnockOut.js и SignalR для достижения динамических представлений, где клиентские события TCP отображаются немедленно, и аналогично действия с WebAPI будут инициировать события в клиентах TCP. Я знаю, это звучит как причудливая комбинация процессов, но все это в соответствии с планом и работает, как и ожидалось:)

Вы уже нашли ответ на свой вопрос?

Я не совсем понимаю, чего вы пытаетесь достичь, ища связь между ними двумя? Хотите ли вы как-то полагаться на TCP/IP для передачи этого идентификатора или в памяти?

Потенциально вы могли бы рассмотреть шаблон Mediator и использовать библиотеку такого рода, которая кажется весьма полезной в случае, который я понял: https://github.com/jbogard/MediatR

В более простом подходе я бы полагался на события, чтобы достичь того, что вы пытаетесь сделать, - это реактивная связь от HTTP-запроса к пользователям C++.

Я понял, что тебе нужно? Мне довольно любопытно решение

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