httpListener 回调被调用两次

httpListener callback gets called twice

我遇到了和这个问题一样的问题Here but using the WebClient class on my client and also the second code example from
那么我该怎么做才能只接到来自我的 WebClient 客户端的一个电话呢?

我的 httplistener 回调被调用了两次,第一次没问题,但第二次在 HttpListenerContext context = Listener.EndGetContext(ar);

处抛出此错误

System.Net.HttpListenerException: 'The I/O operation has been aborted because of either a thread exit or an application request'

服务器代码:

private void DoWork(object arg)
    {
        Listener = new HttpListener();
        Listener.Prefixes.Add("https://+:28210");
        Listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
        Console.WriteLine("Listening...");
        Listener.Start();
        Listener.BeginGetContext(ListenerContext, null);
        Console.ReadKey();      
    }

`

 private static void ListenerContext(IAsyncResult ar)
    {
        Console.WriteLine("Get Data...");
        HttpListenerContext context = Listener.EndGetContext(ar);
        HttpListenerRequest request = context.Request;
        HttpListenerResponse response = context.Response;
        HttpListenerBasicIdentity identity = (HttpListenerBasicIdentity)context.User.Identity;
        Listener.BeginGetContext(ListenerContext, null);
        Console.WriteLine("Got Data!");

        //Some more Code...

        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseData);
        response.ContentLength64 = buffer.Length;
        System.IO.Stream output = response.OutputStream;
        output.Write(buffer, 0, buffer.Length);
    }



客户代码:

using (WebClient client = new WebClient())
            {
                string serialisedData = JsonConvert.SerializeObject(Data);
                client.Credentials = new NetworkCredential(config.UserData.Username, config.UserData.Password);
                byte[] responsebyte = client.UploadData(config.ServerAddress, System.Text.Encoding.UTF8.GetBytes(serialisedData));
                response = System.Text.Encoding.UTF8.GetString(responsebyte);
            }

HttpListener's 文档中的示例可用于处理 一个调用。要处理更多调用,listener.Start()listener.Stop() 之间的代码必须在循环中 运行。

要使此代码异步,只需使用 HttpListener.GetContextStream.Write 的异步版本:

public static async Task  ListenAsync(params string[] prefixes)
{
    if (prefixes == null || prefixes.Length == 0)
    throw new ArgumentException("prefixes");

    using(var listener = new HttpListener())
    {
        // Add the prefixes.
        foreach (string s in prefixes)
        {
            listener.Prefixes.Add(s);
        }
        listener.Start();
        Console.WriteLine("Listening...");
        for (int i=0;i<3;i++)
        {
            var context = await listener.GetContextAsync();
            Console.WriteLine($"Got {i}");

            var response = context.Response;
            string responseString = $"<HTML><BODY> Hello world {i}!</BODY></HTML>";
            var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
            response.ContentLength64 = buffer.Length;
            using(var output = response.OutputStream)
            {
                await output.WriteAsync(buffer,0,buffer.Length);
            }
        }
        listener.Stop();
    }
}

ListenAsync 必须只调用一次并等待它完成。在这种情况下,它在退出前循环处理最多 3 个请求。

在控制台应用程序中调用它可以很简单:

static async Task Main(string[] args)
{
    Console.WriteLine("Starting !");
    await ListenAsync(@"http://*:19999/");
    Console.WriteLine("Finished");
}

要以线程安全的方式停止侦听器,必须使用 CancellationToken 来表示侦听器必须取消。 GetContextAsync() itself can't accept a cancellation token. It can be aborted though by calling HttpListener.Abort。如果发生这种情况时 GetContextAsync() 正在等待,则会抛出 ObjectDisposedException

main 方法现在等待按键,然后发出取消信号并等待 ListenAsync 完成其当前请求:

static async Task Main(string[] args)
{
    Console.WriteLine("Starting !");
    using(var cts=new CancellationTokenSource())
    {
        try
        {
            var task= ListenAsync(cts.Token, @"http://*:19999/");
            Console.ReadKey();
            cts.Cancel();
            await task;
        }
        catch(ObjectDisposedException)
        {
            Console.WriteLine("Listener aborted");
        }
    }
    Console.WriteLine("Finished");
}

ListenAsync 本身在取消令牌上使用 token.Register(()=>listener.Abort()); 来中止侦听器。 for 循环变为 while(!token.IsCancellationRequested),允许监听器一直监听直到按下一个键:

public static async Task  ListenAsync(CancellationToken token,params string[] prefixes)
{
    if (prefixes == null || prefixes.Length == 0)
    throw new ArgumentException("prefixes");

    using(var listener = new HttpListener())
    {
        foreach (string s in prefixes)
        {
            listener.Prefixes.Add(s);
        }
        listener.Start();
        Console.WriteLine("Listening. Hit any key to end.");

        //Abort if the token is signalled            
        token.Register(()=>listener.Abort());

        int i=0;
        //Loop until cancellation is requested
        while (!token.IsCancellationRequested)
        {
            var context = await listener.GetContextAsync();
            Console.WriteLine($"Got {i++}");

            var response = context.Response;
            string responseString = $"<HTML><BODY> Hello world {i}!</BODY></HTML>";
            var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
            response.ContentLength64 = buffer.Length;
            using(var output = response.OutputStream)
            {
                await output.WriteAsync(buffer,0,buffer.Length);
            }
        }
        listener.Stop();
    }
}