.NET Core 中用于 tcp 服务器的 IHostedService
IHostedService for tcp servers in .NET Core
我正在尝试构建一个带有 asp.net 核心的小型 tcp server/daemon 作为与服务器交互的 Web 前端。我发现 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 核心应用程序和守护进程应该如何合作,是否有一个通用模式?
我如何从 Controller 与 运行 服务本身交互?
IHostedService 是否可用于此目的,或者它是否是完全分离 Asp.Net 前端和 service/server 的更好方法,例如通过 运行 守护进程和 asp.net 作为具有某种 IPC 机制的独立进程?
Is there a common pattern for how modern Asp.Net core applications and daemons should cooperate?
实际上,托管服务目前还没有那么强大。所以人们通常使用第三种产品。
但是,可以与托管服务和控制器进行通信。我将以您的代码为例来实现这些目标:
TcpServer
能够接收两个命令,因此我们可以从 TcpClient
切换托管服务的状态。
WebServer
的控制器可以间接调用 TcpServer
的方法(通过中介),并将其呈现为 html
将控制器与托管服务结合起来不是一个好主意。要从托管服务调用方法,我们可以引入一个 Mediator 。调解器只不过是一个充当单例的服务(因为它会被托管服务引用):
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();
}
}
我的建议类似于@itminus
取决于您想要的场景:
- 如果您只想从同一个应用程序内部访问服务 controllers/pages:
不创建 TCP 侦听器。使用请求的后台队列和处理请求的后台服务,从文档中解释的代码调用
- 如果您想通过 TCP 从其他 servers/clients 等访问该服务并从托管 aspcore 应用程序内部访问该服务:
按照第 1 点实现单独的处理服务(逻辑服务器)。您可以注入它并从您的 TCP 侦听器后台服务和控制器调用。
当然你可以从同一个应用程序通过HttpClient
访问你自己的服务,但是使用整个TCP堆栈进行内部调用似乎很奇怪。
如果TCP处理完全独立于web应用,那么就把TCP服务切掉,分离服务器应用。请参阅 docs,了解如何在 dotnet core 2.1 中创建 "pure" 服务而无需 asp/kestrel 开销。
我正在尝试构建一个带有 asp.net 核心的小型 tcp server/daemon 作为与服务器交互的 Web 前端。我发现 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 核心应用程序和守护进程应该如何合作,是否有一个通用模式?
我如何从 Controller 与 运行 服务本身交互?
IHostedService 是否可用于此目的,或者它是否是完全分离 Asp.Net 前端和 service/server 的更好方法,例如通过 运行 守护进程和 asp.net 作为具有某种 IPC 机制的独立进程?
Is there a common pattern for how modern Asp.Net core applications and daemons should cooperate?
实际上,托管服务目前还没有那么强大。所以人们通常使用第三种产品。 但是,可以与托管服务和控制器进行通信。我将以您的代码为例来实现这些目标:
TcpServer
能够接收两个命令,因此我们可以从TcpClient
切换托管服务的状态。WebServer
的控制器可以间接调用TcpServer
的方法(通过中介),并将其呈现为 html
将控制器与托管服务结合起来不是一个好主意。要从托管服务调用方法,我们可以引入一个 Mediator 。调解器只不过是一个充当单例的服务(因为它会被托管服务引用):
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();
}
}
我的建议类似于@itminus
取决于您想要的场景:
- 如果您只想从同一个应用程序内部访问服务 controllers/pages:
不创建 TCP 侦听器。使用请求的后台队列和处理请求的后台服务,从文档中解释的代码调用
- 如果您想通过 TCP 从其他 servers/clients 等访问该服务并从托管 aspcore 应用程序内部访问该服务:
按照第 1 点实现单独的处理服务(逻辑服务器)。您可以注入它并从您的 TCP 侦听器后台服务和控制器调用。
当然你可以从同一个应用程序通过
HttpClient
访问你自己的服务,但是使用整个TCP堆栈进行内部调用似乎很奇怪。如果TCP处理完全独立于web应用,那么就把TCP服务切掉,分离服务器应用。请参阅 docs,了解如何在 dotnet core 2.1 中创建 "pure" 服务而无需 asp/kestrel 开销。