Question

I am looking to build an httpListener into a small server app. Whilst reading up on it, i encountered the following code snippet in this question on stackoverflow

Public Class HTTPServer

Shared Listener As HttpListener = New HttpListener

Public Shared Sub Start()

    ServicePointManager.DefaultConnectionLimit = 500
    ServicePointManager.Expect100Continue = False
    ServicePointManager.MaxServicePoints = 500

    Listener.Prefixes.Add("http://localhost/")
    Listener.Start()

    For i As Integer = 1 To (System.Environment.ProcessorCount * 2)

        Dim NewThread As New System.Threading.Thread(AddressOf ListenerThread)
        NewThread.Priority = ThreadPriority.Normal
        NewThread.IsBackground = True
        NewThread.Start()

    Next

End Sub



Private Shared Sub ListenerThread()

    Dim SyncResult As IAsyncResult

    While True

        SyncResult = Listener.BeginGetContext(New AsyncCallback(AddressOf ListenerCallback), Listener)
        SyncResult.AsyncWaitHandle.WaitOne()

    End While

End Sub



Private Shared Sub ListenerCallback(ByVal StateObject As IAsyncResult)

    Dim Listener As HttpListener = DirectCast(StateObject.AsyncState, HttpListener)

    Dim Context As HttpListenerContext = Listener.EndGetContext(StateObject)
    Dim Request As HttpListenerRequest = Context.Request

    Dim Response As HttpListenerResponse = Context.Response

    Dim ResponseString As String = "OK"

    Dim Buffer As Byte() = System.Text.Encoding.UTF8.GetBytes(ResponseString)
    Response.ContentLength64 = Buffer.Length
    Dim OutputStream As System.IO.Stream = Response.OutputStream
    OutputStream.Write(Buffer, 0, Buffer.Length)

    OutputStream.Close()
    OutputStream.Dispose()

End Sub

End Class

Which seemed pretty simple, and seems much like the msdn example. However when testing it in a dummy project, i found a few things that confused me, for example UI objects can be accessed directly from the callback sub, which i thought should cause a cross threading exception.

To clarify, i modified this code slightly to run within the main form of a simple winforms project.

It seems i do not fully understand the code, for example AsyncWaitHandle.WaitOne() is completely new to me.

Could someone briefly walk me through this snippet please? Any help appreciated.

Was it helpful?

Solution

This snippet code looks like:

  1. It create a HttpListener and start linsening at the uri;
  2. It create some threads (number of System.Environment.ProcessorCount * 2) to send async request to listener try to get a context;
  3. Then the thread was blocked by call SyncResult.AsyncWaitHandle.WaitOne(), about the AsyncWaitHandle.WaitOne please check out Details of AsyncWaitHandle.WaitOne
  4. If some client request come in, the ListenerCallback() will be trigged, and it try to send response back to client
  5. Then go to the next round of loop.

Because it called SyncResult.AsyncWaitHandle.WaitOne() to block that thread, it will get the same result with Synchronously call.

In my opinion, it just returns a simple OK response, so we even don't need any other threads. I will show you the code soon (My code will not work very well if we have heavy operations, and please don't do this in form application.)

    static void Main(string[] args)
    {
        ServicePointManager.DefaultConnectionLimit = 500;
        ServicePointManager.Expect100Continue = false;
        ServicePointManager.MaxServicePoints = 500;

        HttpListener listener = new HttpListener();
        listener.Prefixes.Add("http://localhost:999/");
        listener.Start();
        listener.BeginGetContext(ListenerCallBack, listener);


        Console.ReadLine();
    }

    private static void ListenerCallBack(IAsyncResult result)
    {
        HttpListener httpListener = (HttpListener) result.AsyncState;
        // Call EndGetContext to complete the asynchronous operation.
        HttpListenerContext context = httpListener.EndGetContext(result);
        HttpListenerRequest request = context.Request;
        // Obtain a response object.
        HttpListenerResponse response = context.Response;
        // Construct a response. 
        string responseString = "<HTML><BODY> Hello world!</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;
        output.Write(buffer,0,buffer.Length);
        // You must close the output stream.
        output.Close();


        // we call BeginGetContext() to send async request again for next coming client
        httpListener.BeginGetContext(ListenerCallBack, httpListener);
    }

In C#5 and Net 4.5, we have sync method, it will much easier:

static void Main(string[] args)
    {
        ServicePointManager.DefaultConnectionLimit = 500;
        ServicePointManager.Expect100Continue = false;
        ServicePointManager.MaxServicePoints = 500;

        HttpListener listener = new HttpListener();
        listener.Prefixes.Add("http://localhost:999/");
        listener.Start();

        while (true)
        {
            var t = listener.GetContextAsync();
            HttpListenerContext context = t.Result;
            HttpListenerRequest request = context.Request;
            // Obtain a response object.
            HttpListenerResponse response = context.Response;
            // Construct a response. 
            string responseString = "<HTML><BODY> Hello world!</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;
            output.Write(buffer, 0, buffer.Length);
            // You must close the output stream.
            output.Close();
        }
    }

For your another question about "cross threading exception" for UI, you are right, the created new Thread will have a null value for SynchronizationContext.Current because it's a thread pool thread, we need do a post() in form's SynchronizationContext. Here are more information http://msdn.microsoft.com/en-us/magazine/gg598924.aspx and http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx.

OTHER TIPS

Simple C# WebServer with HttpListener (name: server), BackgroundWorker (name: bw_server), 2 buttons (name: btn_start & btn_stop) and TextBox (name: tb_log):

public partial class Form1 : Form
{
    HttpListener server;
    private void btn_start_Click(object sender, EventArgs e)
    {
        bw_server.RunWorkerAsync(@"http://+:80/");
    }

    private void bw_server_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        string prefix = e.Argument.ToString();
        bw_server.ReportProgress(0, "Starting server...");
        if (!bw_server.CancellationPending)
        {
            try
            {
                start_server(prefix);
            }
            catch (Exception ex)
            {
                bw_server.ReportProgress(0, ex.Message);
                bw_server.CancelAsync();
            }
        }
        else
        {
            e.Cancel = true;
            return;
        }
    }

    private void bw_server_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        log(e.UserState.ToString());
    }

    private void bw_server_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            log("Server was stopped");
        }
        else
        {
            log("Server work was completed");
        }
    }

    private void log(string msg)
    {
        if (String.IsNullOrEmpty(msg)) return;
        tb_log.AppendText((tb_log.Text == String.Empty ? String.Empty : Environment.NewLine) + DateTime.Now.ToLongTimeString() + " " + msg);
    }

    private void btn_stop_Click(object sender, EventArgs e)
    {
        WebRequest.Create("http://localhost:" + port + "/?stop=1").GetResponse();
        bw_server.CancelAsync();
    }

    private void start_server(string prefix)
    {
        server = new HttpListener();
        if (!HttpListener.IsSupported) return;
        if (String.IsNullOrEmpty(prefix))
        {
            throw new ArgumentException("prefix");
        }
        server.Prefixes.Add(prefix);
        server.Start();
        while (server.IsListening)
        {
            HttpListenerContext context = server.GetContext();
            HttpListenerRequest request = context.Request;
            if (request.HttpMethod == "GET")
            {
                HttpListenerResponse response = context.Response;
                response.ContentType = "text/html; charset=UTF-8";
                if (request.QueryString["stop"] == "1")
                {
                    write_to_resp(response, "server was stopped");
                    server.Stop();
                }
                else
                {
                    write_to_resp(response, "bad params");
                }
            }
        }
    }

    private static void write_to_resp(HttpListenerResponse response, string str_resp)
    {
        byte[] buffer = Encoding.UTF8.GetBytes(str_resp);
        response.ContentLength64 = buffer.Length;
        response.OutputStream.Write(buffer, 0, buffer.Length);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top