正确的方法是如何使TcpListener异步处理连接?

3
抱歉内容有点长。我想使用 TcpListener 来监听端口,在不同(后台)线程中处理由传入连接请求产生的重负载,并在准备好响应时将其发送回客户端。我已经阅读了许多 MSDN 上的代码和示例,针对服务器,我提出以下实现方法。
对于下面的所有实现,请假设以下变量:
let sva = "127.0.0.1"
let dspt = 32000

let respondToQuery (ns_ : NetworkStream) (bta_ : byte array) : unit =
    // DO HEAVY LIFTING
    ()

实现方式1(普通同步服务器;我对此MSDN页面中的代码进行了翻译)

let runSync () : unit =
    printfn "Entering runSync ()"
    let (laddr : IPAddress) = IPAddress.Parse sva
    let (svr : TcpListener) = new TcpListener (laddr, dspt)
    try
        svr.Start ()
        let (bta : byte array) = Array.zeroCreate<byte> imbs
        while true do
            printfn "Listening on port %d at %s" dspt sva
            let (cl : TcpClient) = svr.AcceptTcpClient ()
            let (ns : NetworkStream) = cl.GetStream ()
            respondToQuery ns bta
            cl.Close ()
        svr.Stop ()
        printfn "Exiting runSync () normally"
    with
        | excp ->
            printfn "Error:  %s" excp.Message
            printfn "Exiting runSync () with error"

实现2(我对MSDN页面上的代码进行的翻译)

let runAsyncBE () : unit =
    printfn "Entering runAsyncBE ()"
    let (tcc : ManualResetEvent) = new ManualResetEvent (false)
    let (bta : byte array) = Array.zeroCreate<byte> imbs
    let datcc (ar2_ : IAsyncResult) : unit =
        let tcpl2 = ar2_.AsyncState :?> TcpListener
        let tcpc2 = tcpl2.EndAcceptTcpClient ar2_
        let (ns2 : NetworkStream) = tcpc2.GetStream ()
        respondToQuery ns2 bta
        tcpc2.Close ()
        tcc.Set () |> ignore
    let rec dbatc (tcpl2_ : TcpListener) : unit =
        tcc.Reset () |> ignore
        printfn "Listening on port %d at %s" dspt sva
        tcpl2_.BeginAcceptTcpClient (new AsyncCallback (datcc), tcpl2_) |> ignore
        tcc.WaitOne () |> ignore
        dbatc tcpl2_
    let (laddr : IPAddress) = IPAddress.Parse sva
    let (tcpl : TcpListener) = new TcpListener (laddr, dspt)
    try
        tcpl.Start ()
        dbatc tcpl
        printfn "Exiting try block"
        printfn "Exiting runAsyncBE () normally"
    with
        | excp ->
            printfn "Error:  %s" excp.Message
            printfn "Exiting runAsyncBE () with error"

实现3(基于MSDN页面上的异步工作流程

let runAsyncA () : unit =
    printfn "Entering runAsyncA ()"
    let (laddr : IPAddress) = IPAddress.Parse sva
    let (svr : TcpListener) = new TcpListener (laddr, dspt)
    try
        svr.Start ()
        let (bta : byte array) = Array.zeroCreate<byte> imbs
        while true do
            printfn "Listening on port %d at %s" dspt sva
            let (cl : TcpClient) = svr.AcceptTcpClient ()
            let (ns : NetworkStream) = cl.GetStream ()
            async {respondToQuery ns bta} |> Async.RunSynchronously
            cl.Close ()
        svr.Stop ()
        printfn "Exiting runAsyncA () normally"
    with
        | excp ->
            printfn "Error:  %s" excp.Message
            printfn "Exiting runAsyncA () with error"

根据我阅读的MSDN文档,我认为实现3应该是最快的。但当我从多台机器上发送多个查询请求时,它们的操作速度都大致相同。这让我相信我一定做错了什么。

实现2实现3是否是实现在后台执行重活并在完成后将响应返回给客户端,同时允许另一个客户端连接并在另一个后台线程中启动另一个任务的TcpListener的“正确”方法?如果不是,请告诉我应该阅读哪些类(或教程)。

1个回答

6

主循环的正确结构应该如下所示:

let respondToQuery (client:TcpClient) = async {
  try
    let stream = client.GetStream()
    () // TODO: The actual processing goes here!
  finally
    client.Close() }

async {
  while true do 
    let! client = t.AcceptTcpClientAsync() |> Async.AwaitTask
    respondToQuery client |> Async.Start }

需要翻译的内容如下:

需要注意的关键点有:

  • 我使用async包装了主循环,这样您就可以使用AcceptTcpClientAsync异步等待客户端(而不会在此处阻塞)

  • respondToQuery函数返回一个异步计算,该计算使用Async.Start在后台启动,以便处理可以与等待下一个客户端并行进行(如果使用Async.RunSynchronously,则会阻塞并等待respondToQuery完成)

  • 要使此过程完全异步,respondToQuery内部的代码还需要使用流的异步操作-请查找AsyncReadAsyncWrite

您还可以使用Async.StartChild,在这种情况下,子计算(respondToQuery的主体)将获得与父计算相同的取消标记,因此当您取消主异步工作流时,它也会取消所有子计算:

格式要求已保留。
  while true do 
    let! client = t.AcceptTcpClientAsync() |> Async.AwaitTask
    do! respondToQuery client |> Async.StartChild |> Async.Ignore }
Async.StartChild方法返回一个异步计算(使用let!do!开始),我们需要忽略它返回的令牌(可以用于等待子进程完成)。

你可以使用 use client = client 替代 try..finally 吗? - J D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接