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

时间:2014-10-24 13:02:39

标签: http windows-services httplistener

我是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 - 有时它会停滞不前,我等着等待。在那段时间我可以看到&#34; 1&#34;在日志中,但不是&#34; 2&#34;。如果我等待,没有任何事情发生。但是,如果我再次加载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非常糟糕,但这只是我的测试。

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

那么,如何以正确的方式做到这一点?我无法找到有关在谷歌接收的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循环产生任何问题

1 个答案:

答案 0 :(得分:6)

你真的想要使用异步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方法的延续发布到不同的任务调度程序 - 默认情况下,是一个线程池任务调度程序。

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

相关问题