gpt4 book ai didi

asynchronous - 使 TcpListener 异步处理连接的正确方法是什么?

转载 作者:行者123 更新时间:2023-12-04 04:36:04 27 4
gpt4 key购买 nike

抱歉发了这么长的帖子。我想使用 TcpListener 来监听端口,处理不同(后台)线程中传入连接请求的繁重工作,然后在准备就绪时将响应发送回客户端。我在MSDN上阅读了大量的代码和示例,并为服务器提出了以下实现。

对于下面的所有实现,请假设以下变量:

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 文档的阅读,我认为实现 3 会是最快的。但是当我用来自多台机器的多个查询访问服务器时,它们都以大致相同的速度运行。这让我相信我一定做错了什么。

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

最佳答案

主循环的正确结构应该是这样的:

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 的主体)获得与父计算相同的取消 token ,因此当您取消主要的异步工作流,它也会取消所有的 child :

  while true do 
let! client = t.AcceptTcpClientAsync() |> Async.AwaitTask
do! respondToQuery client |> Async.StartChild |> Async.Ignore }

Async.StartChild 方法返回一个异步计算(使用 let!do! 开始),我们需要忽略它返回的 token (可用于等待 child 完成)。

关于asynchronous - 使 TcpListener 异步处理连接的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15272305/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com