【问题标题】:httpListener callback gets called twicehttpListener 回调被调用两次
【发布时间】:2018-11-29 12:48:38
【问题描述】:

我也有类似Here 的问题,但在我的客户端上使用了 WebClient 类以及来自this answere 的第二个代码示例。
那么我该怎么做才能只接到来自我的 WebClient 客户端的一个电话?

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

System.Net.HttpListenerException: 'I/O 操作已因线程退出或应用程序请求而中止'

服务器代码:

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);
            }

【问题讨论】:

  • 没有得到答案的问题通常需要改进,而不是碰撞。 Thread 异常中止与“调用两次”无关,这意味着代码中存在线程问题。也许在应用程序终止时请求还没有完成? EndGetContext 和其余代码在哪里?
  • BTW async/await 被引入以避免这些错误。而不是BeginGetContext 使用GetContextAsync,例如var context=await listener.GetContextAsync();
  • 完整异常包含导致异常的调用堆栈和任何内部异常。您可以通过Exception.ToString() 轻松获取。调用堆栈将显示问题实际发生的位置以及导致问题的路径。将完整的异常与您的代码一起发布
  • 第二次调用ListenerContext 时出现异常。如果我启动我的客户端,ListenerContext 在没有问题的情况下运行,但奇怪的是它在第一个完成后第二次被调用。 EndGetContext 位于 ListenerContext 的第 2 行。代码的其余部分只是与数据一起发生的简单内容。我可以尝试使用GetContextAsync
  • 并且异常消息清楚地表明某事中止了操作。也许是原始的 HttpListener 被 GC 处理掉了——DoWork 每次都会启动一个新实例。或者应用程序在没有取消操作的情况下终止。这就是其余代码很重要的原因。

标签: c# .net webclient httplistener


【解决方案1】:

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() 本身不能接受取消令牌。它可以通过调用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(()=&gt;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();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-23
    • 1970-01-01
    • 2012-02-06
    • 1970-01-01
    • 2017-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多