2013-03-07 147 views
3

對不起,這個帖子很長。我想使用TcpListener來偵聽端口,處理不同(後臺)線程中傳入連接請求的繁重提升,然後在準備就緒時將響應發送回客戶端。我已經閱讀了MSDN上的很多代碼和示例,併爲服務器提供了以下實現。使TcpListener異步處理連接的正確方法是什麼?

對於所有下面的實施方式的,請假設以下變量:

let sva = "127.0.0.1" 
let dspt = 32000 

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

實施1(普通紙,同步服務器;我的the code from this MSDN page翻譯)

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 (我的翻譯代碼on this MSDN page

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(我基於the MSDN page for asynchronous workflows實現)現在

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文檔的閱讀,我本來以爲Implementation 3將是最快的。但是當我用多臺機器的多個查詢來訪問服務器時,它們的運行速度大致相同。這使我相信我一定在做錯事。

或者是Implementation 2Implementation 3「正確」的方式來實現一個TcpListener,做了繁重的背景下,當它完成,同時允許其他客戶或許還連接並開始另一個返回到客戶端的響應任務在另一個後臺線程?如果沒有,請告訴我應該閱讀哪些類(或教程)?

回答

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函數返回一個n表示在後臺使用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!開始),我們需要忽略它返回(可用於等到孩子完成)令牌。

+0

非常感謝! – Shredderroy 2013-03-07 14:59:40

+0

你可以使用'use client = client'而不是'try..finally'嗎? – 2013-03-10 12:10:16

相關問題