【问题标题】:How to receive http-requests with Windows Service如何使用 Windows 服务接收 http 请求
【发布时间】:2014-12-20 07:44:37
【问题描述】:

我是 winservice 开发的新手。我需要创建一个服务,它将包含一些函数和 api:当一些请求到来时它应该做一些事情。

据我了解,我需要使用 HttpListener 类。

这里描述了这个问题的基础:How to create a HTTP request listener Windows Service in .NET 但它还没有完成代码,我还有更多问题。

这是我的代码:

partial class MyService
{
    private System.ComponentModel.IContainer components = null;
    private System.Diagnostics.EventLog eventLog1;
    private HttpListener listener;


    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
        this.eventLog1 = new System.Diagnostics.EventLog();
        ((System.ComponentModel.ISupportInitialize)(this.eventLog1)).BeginInit();

        this.ServiceName = Globals.ServiceName;
        ((System.ComponentModel.ISupportInitialize)(this.eventLog1)).EndInit();

        HttpListener listener = new HttpListener();
        listener.Prefixes.Add("http://localhost:1111/");
        listener.Start();
        listener.BeginGetContext(new AsyncCallback(OnRequestReceive), listener);

    }
}

public partial class MyService : ServiceBase
{
    public MyService()
    {
        InitializeComponent();
        if (!System.Diagnostics.EventLog.SourceExists("MySource"))
        {
            System.Diagnostics.EventLog.CreateEventSource(
                "MySource", "MyNewLog");
        }
        eventLog1.Source = "MySource";
        eventLog1.Log = "MyNewLog";
    }

    // smth about onstart and onstop

    private void OnRequestReceive(IAsyncResult result)
    {
        eventLog1.WriteEntry("Connection catched!");

        HttpListener listener = (HttpListener)result.AsyncState;
        eventLog1.WriteEntry("1");
        HttpListenerContext context = listener.GetContext();
        eventLog1.WriteEntry("2");
        HttpListenerRequest request = context.Request;
        eventLog1.WriteEntry("3");

        HttpListenerResponse response = context.Response;
        eventLog1.WriteEntry("4");

        string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
        response.ContentLength64 = buffer.Length;


        Stream output = response.OutputStream;
        output.Write(buffer, 0, buffer.Length);
        output.Close();
    }
}

所以,我添加了一些日志来了解发生了什么:

当我重新安装并启动服务时,我打开 http://localhost:1111/ 并 - 有时一切正常,我在浏览器中看到 HelloWorld,在 MyLog 中看到 1234 - 有时它卡住了,我等待,等待,等待。那时我可以在日志中看到“1”,但看不到“2”。如果我只是等待,什么都不会发生。但是,如果我再次加载 localhost(或按 f5),我可以看到 Hello World... 而日志只是添加了 234,它是 1234 而不是 11234 总结..

在那之后,它只是卡住了,在我重新启动服务之前,日志中没有任何变化。

所以,我明白了,这个问题可能出在listener.EndGetContext,尚未使用。

我在代码中测试了这个变化(最后两行):

private void OnRequestReceive(IAsyncResult result)
    {
        eventLog1.WriteEntry("Connection catched!");

        HttpListener listener = (HttpListener)result.AsyncState;
        eventLog1.WriteEntry("1");
        HttpListenerContext context = listener.GetContext();
        eventLog1.WriteEntry("2");
        HttpListenerRequest request = context.Request;
        eventLog1.WriteEntry("3");

        HttpListenerResponse response = context.Response;
        eventLog1.WriteEntry("4");

        string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
        response.ContentLength64 = buffer.Length;


        Stream output = response.OutputStream;
        output.Write(buffer, 0, buffer.Length);
        output.Close();

        listener.EndGetContext(result);
        listener.BeginGetContext(new AsyncCallback(OnRequestReceive), listener);
    }

这个糟糕的代码有效:当显示 HelloWorld 时,我可以重新加载页面并再次查看它(并且日志多次显示 1234)。但是我仍然对 1......234 有问题(对于某些加载的 HelloWorld 会出现,对于有些则不会)

当然,在回调结束时使用 BeginGetContext 是很糟糕的,但这只是我的测试。

另外,我需要处理并行请求

那么,如何以正确的方式做到这一点?我找不到任何关于在 google 中接收 winservices 请求的有用信息。只是关于 HttpListener 的问题 ..

更新:

我尝试了新方法:

HttpListener listener = new HttpListener();
listener.Prefixes.Add(Globals.URL);
listener.Start();

listenerThread = new Thread(() =>
{
    while (listener.IsListening)
    {
         HandleRequest(listener.GetContext());
    }

});
listenerThread.Start();

一切正常,当上下文出现时它会等待,使用它并再次等待。但是这种方式会创建一个请求队列。如果一次有1000个请求,最后一个会在999之后处理。总比没有好,但我想并行处理请求。此外,发生了非常奇怪的事情:我创建了带有一些路由的简单网络服务器,但使用一段时间后,它挂起。在我重新安装(不是重新启动!)服务之前没有任何变化。

所以,我仍在等待任何真正好的实现,它将包含

  • 并行处理传入请求
  • 用于理解回调等的清晰简单的代码
  • 停止服务不会对 while 循环造成任何问题

【问题讨论】:

    标签: http windows-services httplistener


    【解决方案1】:

    您确实想为此使用异步 API - 事实上,这几乎正是它的设计目的。

    基本代码比较简单:

    void Main()
    {
        var cts = new CancellationTokenSource();
    
        var task = StartListener(cts.Token);
    
        Console.ReadLine();
    
        cts.Cancel();
    
        task.Wait();
    }
    
    async Task StartListener(CancellationToken token)
    {
      var listener = new HttpListener();
      listener.Start();
    
      token.Register(() => listener.Abort());
    
      while (!token.IsCancellationRequested)
      {
        HttpListenerContext context;
    
        try
        {
          context = await listener.GetContextAsync().ConfigureAwait(false);
    
          HandleRequest(context); // Note that this is *not* awaited
        }
        catch
        {
          // Handle errors
        }
      }
    }
    
    async Task HandleRequest(HttpListenerContext context)
    {
      // Handle the request, ideally in an asynchronous way.
      // Even if not asynchronous, though, this is still run 
      // on a different (thread pool) thread
    }
    

    如果您没有使用 await 模式的经验,您可能需要阅读一些详细信息。当然,这绝对不是生产代码,只是一个示例。您需要为初学者添加更多错误处理。

    另外值得注意的是,因为 HandleRequest 没有被等待,你不能在 StartListener 方法中处理它的异常——你要么必须添加一个继续来处理这些异常,要么你应该只捕获异常直接在HandleRequest

    基本思想很简单 - 通过不等待 HandleRequest 方法,我们立即返回等待新请求(通过异步 I/O,即无线程)。如果HttpListener 真的打算以这种方式使用(我相信是这样,虽然我没有测试过),它将允许您并行处理许多请求,包括异步 I/O 和 CPU 工作。

    ConfigureAwait 确保您不会与SynchronizationContext 同步。这在服务/控制台应用程序中并不是绝对必要的,因为它们通常没有任何同步上下文,但无论如何我倾向于明确地写出来。 GetContextAsync 方法的延续因此被发布到不同的任务调度程序 - 默认情况下,线程池任务调度程序。

    在您的代码中实现此功能时,您只需将ReadLine 之前的Main 代码用于OnStart,并使用OnStop 之后的代码。 task.Wait() 可以确保您完全关闭 - 实际关闭所有内容可能需要一段时间。此外,StartListener 本身中的任何异常都将被Wait 重新抛出,因此您还希望在那里记录一些错误。

    【讨论】:

    • 非常感谢您提供此代码!我忘了,你没有为监听器添加前缀,但它确实有效!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-28
    • 2013-07-19
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    • 1970-01-01
    相关资源
    最近更新 更多