Связь между службой 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++.
Я понял, что тебе нужно? Мне довольно любопытно решение