首页 文章

如何使用Windows服务接收http请求

提问于
浏览
2

我是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 ...并且log只添加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之后处理 . 它总比没有好,但我想并行处理请求 . 此外,发生了非常奇怪的事情:我创建了一些带有一些路由的简单web服务器,但经过一段时间的使用后,它就会挂起 . 在我重新安装(不重启!)服务之前没有任何改变 .

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

  • 并行处理传入请求

  • 清晰简单的代码,用于理解回调等

  • 停止服务不应该在while循环中出现任何问题

1 回答

  • 4

    你真的想要使用异步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 重新抛出,因此您也希望在那里记录一些错误 .

相关问题