使TcpListener异步处理连接的正确方法是什么?
对不起,这个帖子很长。我想使用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 2
或Implementation 3
“正确”的方式来实现一个TcpListener
,做了繁重的背景下,当它完成,同时允许其他客户或许还连接并开始另一个返回到客户端的响应任务在另一个后台线程?如果没有,请告诉我应该阅读哪些类(或教程)?
主回路正确的结构应该是这样的:
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
内部的代码也需要使用流的异步操作 - 查找AsyncRead
和AsyncWrite
。
你也使用Async.StartChild
,在这种情况下,孩子的计算(的respondToQuery
体),得到了相同的取消标记为母公司,所以当你取消主异步工作流程,将取消所有的孩子太:
while true do
let! client = t.AcceptTcpClientAsync() |> Async.AwaitTask
do! respondToQuery client |> Async.StartChild |> Async.Ignore }
的Async.StartChild
方法返回一个异步计算(使用let!
或do!
开始),我们需要忽略它返回(可用于等到孩子完成)令牌。
非常感谢! – Shredderroy 2013-03-07 14:59:40
你可以使用'use client = client'而不是'try..finally'吗? – 2013-03-10 12:10:16