Topshelf服务(充当TCP服务器)和自主OWIN WebAPI

时间:2017-01-09 20:10:48

标签: c# asp.net-web-api tcp owin topshelf

我有一个Topshelf Windows服务,充当TCP服务器。在这项服务中,我还有一个自托管(OWIN)WebAPI。

我的目标是以某种方式允许WebAPI与包含并运行在同一服务中的TCP服务器进行通信。当然,我可以简单地使用像“触发器”文件或共享数据库那样的东西。虽然我想了解更多优化/原生的方法来实现这一目标,但我可以经常进行民意调查。

为了更好地了解项目,请考虑单个页面应用程序使用我的API并使用任意字符串参数进行某些调用。然后,应将此数据传递给连接到正在运行的TCP服务器的客户端(使用winsock的C ++控制台应用程序)。

实例化以下Container并将其传递给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调用传递到Windows服务中的_engineService对象?也许类似于WPF的MVVM Light Messenger?这个想法是它将被解析并发送到存储在客户端列表中的适当的TcpClient。

有关如何实现这一目标的任何建议将不胜感激。请随时要求澄清/更多代码。

3 个答案:

答案 0 :(得分:0)

您是否找到了问题的答案?

我不太明白你试图在两者之间寻求沟通的目的是什么?你想以某种方式依靠TCP / IP来传递这个id或内存吗?

潜在地,您可以考虑使用Mediator模式并使用这种在我理解的情况下看起来非常有用的库:https://github.com/jbogard/MediatR

在一种更简单的方法中,我会依靠事件来实现您要做的事情,即从HTTP请求到c ++用户的反应通信。

我了解你的需要吗?我对解决方案很好奇

答案 1 :(得分:0)

我假设您正在尝试获取HTTP GET请求的ID参数并将其发送到连接到EngineService的TCP客户端。如果在ApiService之前初始化了EngineService,我认为这是一个如何从ApiService的控制器实例中获取一个唯一的EngineService实例的问题。

如果我关注您,您可以将EngineService设置为ContainerService的公共静态属性,并将其作为ContainerService.EngineService从控制器(或应用程序中的任何位置)引用,或者更好地将您的EngineService注册为DI容器中的单例将其注入ApiService。

答案 2 :(得分:0)

解决方案(调用WebAPI触发EngineService)

我现在使用RabbitMQ / EasyNetQ来实现WebApi和包含我的TCP客户端的EngineService对象之间的通信。

我现在偶然将它们分成两个独立的Projects / Topshelf服务。

以下是新的“通信”组件,它在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客户端中的事件。我知道这听起来像是一个奇怪的过程组合,但它完全按计划进行,并按预期工作:)