如何使用 HttpListener 同时处理多个连接?

How to process multiple connections simultaneously with HttpListener?

在我构建的应用程序中,需要可以同时为多个客户端提供服务的网络服务器。
为此,我使用 HttpListener 对象。及其 Async methods\events 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");
    }

输出是

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

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 似乎不支持流水线,而是在处理第二个请求之前完成对第一个请求的响应。因此,确保每个请求都通过新连接发送很重要。

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

试试这个..

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

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