【问题标题】:How to process multiple connections simultaneously with HttpListener?如何使用 HttpListener 同时处理多个连接?
【发布时间】:2015-04-01 03:21:07
【问题描述】:

在我构建的应用程序中,需要可以同时为多个客户端提供服务的网络服务器。
为此,我使用 HttpListener 对象。及其Async方法\事件BeginGetContextEndGetContext
在委托的方法中,有一个调用让监听器重新开始监听,并且它起作用了..主要是。

提供的代码是我在这里和那里找到的代码的混合,以及延迟,以模拟数据处理瓶颈。

问题是,它仅在提供最后一个连接后才开始管理下一个连接.. 对我没有用。

public class HtServer {


    public void startServer(){
        HttpListener HL = new HttpListener();
        HL.Prefixes.Add("http://127.0.0.1:800/");
        HL.Start();
        IAsyncResult HLC = HL.BeginGetContext(new AsyncCallback(clientConnection),HL);   
    }

    public void clientConnection(IAsyncResult res){
        HttpListener listener = (HttpListener)res.AsyncState;
        HttpListenerContext context = listener.EndGetContext(res);
        HttpListenerRequest request = context.Request;
        // Obtain a response object.
        HttpListenerResponse response = context.Response;
        // Construct a response. 
        // add a delay to simulate data process
        String before_wait = String.Format("{0}", DateTime.Now);
        Thread.Sleep(4000);
        String after_wait = String.Format("{0}", DateTime.Now);
        string responseString = "<HTML><BODY> BW: " + before_wait + "<br />AW:" + after_wait + "</BODY></HTML>";
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
        // Get a response stream and write the response to it.
        response.ContentLength64 = buffer.Length;
        System.IO.Stream output = response.OutputStream;
        // You must close the output stream.
        output.Write(buffer, 0, buffer.Length);
        output.Close();
        listener.BeginGetContext(new AsyncCallback(clientConnection), listener);
    }
}

编辑

    private static void OnContext(IAsyncResult ar)
    {
        var ctx = _listener.EndGetContext(ar);
        _listener.BeginGetContext(OnContext, null);

        Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss.fff") + " Handling request");

        var buf = Encoding.ASCII.GetBytes("Hello world");
        ctx.Response.ContentType = "text/plain";

        // prevent thread from exiting.
        Thread.Sleep(3000);
        // moved these lines here.. to simulate process delay
        ctx.Response.OutputStream.Write(buf, 0, buf.Length);
        ctx.Response.OutputStream.Close();
        Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss.fff") + " completed");
    }

输出是

【问题讨论】:

    标签: c# asynchronous httplistener


    【解决方案1】:

    嗯。那是因为您在处理完第一个上下文后开始获取下一个上下文。不要那样做。直接获取下一个上下文:

    public void clientConnection(IAsyncResult res){
        HttpListener listener = (HttpListener)res.AsyncState;
        HttpListenerContext context = listener.EndGetContext(res);
    
        //tell listener to get the next context directly.
        listener.BeginGetContext(clientConnection, listener);
    
        HttpListenerRequest request = context.Request;
        // Obtain a response object.
        HttpListenerResponse response = context.Response;
        // Construct a response. 
        // add a delay to simulate data process
        String before_wait = String.Format("{0}", DateTime.Now);
        Thread.Sleep(4000);
        String after_wait = String.Format("{0}", DateTime.Now);
        string responseString = "<HTML><BODY> BW: " + before_wait + "<br />AW:" + after_wait + "</BODY></HTML>";
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
        // Get a response stream and write the response to it.
        response.ContentLength64 = buffer.Length;
        System.IO.Stream output = response.OutputStream;
        // You must close the output stream.
        output.Write(buffer, 0, buffer.Length);
        output.Close();
    }
    

    这是我的示例代码,证明它可以工作(根据 OP 的请求更新):

    class Program
    {
        private static HttpListener _listener;
    
        static void Main(string[] args)
        {
            _listener = new HttpListener();
            _listener.Prefixes.Add("http://localhost/asynctest/");
            _listener.Start();
            _listener.BeginGetContext(OnContext, null);
    
            Console.ReadLine();
        }
    
        private static void OnContext(IAsyncResult ar)
        {
            var ctx = _listener.EndGetContext(ar);
            _listener.BeginGetContext(OnContext, null);
    
            Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss.fff") + " Handling request");
    
            var buf = Encoding.ASCII.GetBytes("Hello world");
            ctx.Response.ContentType = "text/plain";
    
            // simulate work
            Thread.Sleep(10000);
    
            ctx.Response.OutputStream.Write(buf, 0, buf.Length);
            ctx.Response.OutputStream.Close();
    
    
            Console.WriteLine(DateTime.UtcNow.ToString("HH:mm:ss.fff") + " completed");
        }
    }
    

    生成:

    两个请求都开始被直接处理。

    为什么上面的代码有效

    HTTP 有一种叫做流水线的东西。这意味着通过同一连接接收的所有请求必须以相同的顺序获得响应。但是,内置的 HttpListener 似乎不支持流水线,而是在处理第二个请求之前完成第一个请求的响应。因此,确保每个请求都通过新连接发送非常重要。

    最简单的方法是在尝试代码时使用不同的浏览器。我这样做了,如您所见,我的两个请求是同时处理的。

    【讨论】:

    • 你确定你用多个连接试过了吗?浏览器倾向于重用现有的连接,但这是行不通的。
    • @yossi 如果您尝试发布当前代码,而不是一些过时的代码。
    • 没有。浏览器应该允许 3 个并发 http 请求。问题很可能是因为您使用的是同步单线程代码。
    • @jgauffin 是的,现在我同时从 3 个不同的浏览器尝试了它。它们之间的时间间隔为 4 秒。可以是Stream 吗?如果是这样,我该如何改变它?
    • @yossi 好吧,鉴于此答案,问题中发布的代码显然是错误的。这个答案的方法是正确的。所以发布你拥有的最好的代码。
    【解决方案2】:

    试试这个吧..

    这将使用异步编码来确保没有阻塞。阻塞意味着当线程休眠时,这通常是程序倾向于“冻结”的方式。通过使用此代码,您可以运行非阻塞,这意味着几乎不可能“冻结”应用程序。

    public async Task handleClientConnection(HttpListener listener){
        HttpListenerContext context = await listener.GetContextAsync();
        var ret = handleClientConnection(listener);
    
        HttpListenerRequest request = context.Request;
        // Obtain a response object.
        HttpListenerResponse response = context.Response;
        // Construct a response. 
        // add a delay to simulate data process
        String before_wait = String.Format("{0}", DateTime.Now);
        await Task.Wait(4000);
        String after_wait = String.Format("{0}", DateTime.Now);
        string responseString = "<HTML><BODY> BW: " + before_wait + "<br />AW:" + after_wait + "</BODY></HTML>";
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
        // Get a response stream and write the response to it.
        response.ContentLength64 = buffer.Length;
        using(System.IO.Stream output = response.OutputStream)
            output.Write(buffer, 0, buffer.Length);
    
        await ret;
    }
    
    public void startServer(){
        HttpListener HL = new HttpListener();
        HL.Prefixes.Add("http://127.0.0.1:800/");
        HL.Start();
        await handleClientConnection(HL);
    }
    

    【讨论】:

    • 哦,我想我真的是 .NET 的新手。我在 xp pro 上使用 vs2010(客户需求)谢谢!
    • 顺便说一句,在这种情况下,我们仍然是单线程的。如果您可能想要多线程...只需将 await listener.GetContextAsync(); 替换为 await listener.GetContextAsync().ConfigureAwait(false);
    • 你永远不会说为什么你的代码应该工作。那么他从你的回答中学到了什么,而不是使用异步 IO 的另一种方式?
    • 这是.Net 4.5,对吗?如果是这样,我不能在xp-pro上使用它,这是客户端的要求。
    • @yossi 那是正确的。但是我会问,为什么这是一个要求。 Microsoft 不再支持 xp。现在运行 XP 是极其危险的(除非您是那些为 Microsoft 的私人支持支付巨额费用的银行之一)。
    猜你喜欢
    • 2013-10-18
    • 2012-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-18
    • 1970-01-01
    • 2014-12-17
    • 1970-01-01
    相关资源
    最近更新 更多