1

我希望将 httpListener 构建到一个小型服务器应用程序中。在阅读它时,我在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

这看起来很简单,看起来很像msdn 示例。然而,在一个虚拟项目中测试它时,我发现了一些让我感到困惑的事情,例如 UI 对象可以直接从回调子访问,我认为这应该会导致跨线程异常。

为了澄清,我稍微修改了这段代码以在一个简单的 winforms 项目的主窗体中运行。

似乎我不完全理解代码,例如 AsyncWaitHandle.WaitOne() 对我来说是全新的。

有人可以简要介绍一下这个片段吗?任何帮助表示赞赏。

4

2 回答 2

2

此片段代码如下所示:

  1. 它创建一个 HttpListener 并在 uri 处开始 linsening;
  2. 它创建一些线程(System.Environment.ProcessorCount * 2 的数量)向侦听器发送异步请求以尝试获取上下文;
  3. 然后线程被调用 SyncResult.AsyncWaitHandle.WaitOne() 阻塞,关于 AsyncWaitHandle.WaitOne 请查看AsyncWaitHandle.WaitOne 的详细信息
  4. 如果有客户端请求进来,ListenerCallback() 将被触发,并尝试将响应发送回客户端
  5. 然后进入下一轮循环。

因为它调用了 SyncResult.AsyncWaitHandle.WaitOne() 来阻塞该线程,所以它会通过Synchronously调用获得相同的结果。

在我看来,它只是返回一个简单的 OK 响应,所以我们甚至不需要任何其他线程。我会尽快给你看代码(如果我们有繁重的操作,我的代码将不能很好地工作,请不要在表单应用程序中这样做。)

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

在 C#5 和 Net 4.5 中,我们有同步方法,它会更容易:

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

对于您关于 UI 的“跨线程异常”的另一个问题,您是对的,创建的新线程将为 SynchronizationContext.Current 具有空值,因为它是线程池线程,我们需要在表单的 SynchronizationContext 中执行 post()。以下是更多信息http://msdn.microsoft.com/en-us/magazine/gg598924.aspxhttp://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx

于 2012-09-04T07:46:15.597 回答
0

带有 HttpListener(名称:server)、BackgroundWorker(名称:bw_server)、2 个按钮(名称:btn_start & btn_stop)和 TextBox(名称:tb_log)的简单 C# WebServer:

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);
    }
}
于 2016-12-05T09:20:52.290 回答