[英]what's wrong on this f# echo server?
我写了这个f#echo服务器:
open System.Net
open System.Net.Sockets
open System.IO
open System
open System.Text
open System.Collections.Generic
let addr = IPAddress.Parse("127.0.0.1")
let listener = new TcpListener(addr, 2000)
listener.Start()
let rec loop2(c:TcpClient,sr:StreamReader,sw:StreamWriter)=async {
let line=sr.ReadLine()
if not(line=null) then
match line with
|"quit"->
sr.Close()
sw.Close()
c.Close()
|_ ->
if line.Equals("left") then
sw.WriteLine("right")
return! loop2(c,sr,sw)
sw.WriteLine(line)
return! loop2(c,sr,sw)
else
sr.Close()
sw.Close()
c.Close()
}
let loop()=async {
while true do
let c=listener.AcceptTcpClient()
let d = c.GetStream()
let sr = new StreamReader(d)
let sw = new StreamWriter(d)
sw.AutoFlush<-true
Async.Start(loop2(c,sr,sw))
}
Async.RunSynchronously(loop())
这个程序可以做到:
但是当我运行编程时,当客户端发送'left',得到'right'并发送'quit'时,我得到了这个异常:
未处理异常:System.ObjectDisposedException :(继续写入已关闭)TextWriter。在Microsoft.FSharp.Control.CancellationTokenOps.Start@1192-1.Invoke(例外e)in。$ Control.loop @ 419-40(Trampoline)这个,FSharpFunc
2 action) in Microsoft.FSharp.Control.Trampoline.ExecuteAction(FSharpFunc
firstAction)中的Microsoft.FSharp.Control.Trampoline.ExecuteAction(FSharpFunc 2 firstAction)中。$ Control.-ctor @ 476-1.Invoke System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback回调)中的System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,ContextCallback回调,Object状态,BooleanpreserveSyncCtx)中System.Threading.QuereadingUserWorkItemCallback.WaitCallback_Context(Object state)中的(对象状态) System.Threading._ThreadPoolWaitC中System.Threading.ThreadPoolWorkQueue.Dispatch()中的System.Threading.Quereading.QuereadingUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()中的,对象状态,布尔值preserveSyncCtx) allback.PerformWaitCallback()。 。 。(按任意键继续)
我该如何解决这个问题?
问题在于,与命令式语言中的同名不同,计算表达式中的return
不会短路。 所以一旦return!
在if line.Equals("right")
返回,即。 在套接字关闭后,运行if
块后的代码并尝试写入已关闭的套接字。 解决方法是将这两行放在else
:
if line.Equals("left") then
sw.WriteLine("right")
return! loop2(c,sr,sw)
else
sw.WriteLine(line)
return! loop2(c,sr,sw)
作为一个额外的风格提示,这整个身体可以实现为match
:
let rec loop2(c:TcpClient,sr:StreamReader,sw:StreamWriter)=async {
let line=sr.ReadLine()
match line with
| null | "quit" ->
sr.Close()
sw.Close()
c.Close()
| "left" ->
sw.WriteLine("right")
return! loop2(c,sr,sw)
| _ ->
sw.WriteLine(line)
return! loop2(c,sr,sw)
}
代码中的问题在于:
if line.Equals("left") then
sw.WriteLine("right")
return! loop2(c,sr,sw)
sw.WriteLine(line)
return! loop2(c,sr,sw)
如果收到“left”,则写入“right”,然后执行嵌套的loop2
直到收到“quit”。 然后,在完成所有这些之后,它会尝试写入line
并执行更多嵌套的loop2
。 当然,到目前为止,您已经处理了连接,因此例外。
看起来写line
应该在else块中,这样可以防止错误:
if line.Equals("left") then
sw.WriteLine("right")
else
sw.WriteLine(line)
return! loop2(c,sr,sw)
当然,您也可以将此检查与模式匹配集成。 下面的示例将在一个结构中处理null检查和每个字符串选项。
let line = Option.ofObj <| sr.ReadLine()
match line with
|None
|Some("quit") ->
sr.Close()
sw.Close()
|Some("left") ->
sw.WriteLine("right")
return! loop2(c,sr,sw)
|Some(line) ->
sw.WriteLine(line)
return! loop2(c,sr,sw)
请注意,您的async
块完全没用,因为您只使用了诸如AcceptTcpClient()
, ReadLine()
和WriteLine()
阻塞函数。 将这些函数放在async
块中并不会使它们异步。 如果你想异步工作,它必须一直异步。
我猜你的目标是在客户端到达时异步接受客户端,在不同的功能中异步处理每个客户端。
这个领域的大多数.NET API是用Task<'T>
而不是F#特定的async<'T>
编写的,所以我建议创建一些辅助函数:
let writeLineAsync (sw:StreamWriter) (text : string) =
sw.WriteLineAsync(text).ContinueWith(fun t -> ())
|> Async.AwaitTask
let readLineAsync (sr:StreamReader) =
sr.ReadLineAsync()
|> Async.AwaitTask
let acceptClientAsync (l : TcpListener) =
l.AcceptTcpClientAsync()
|> Async.AwaitTask
然后,您可以创建一个正确的异步版本:
let rec handleClient (c:TcpClient) (sr:StreamReader) (sw:StreamWriter) =
async {
let! line = readLineAsync sr
match Option.ofObj(line) with
|None
|Some("quit")->
sr.Close()
sw.Close()
|Some("left") ->
do! writeLineAsync sw "right"
return! loop2(c,sr,sw)
|Some(line) ->
do! writeLineAsync sw line
return! loop2(c,sr,sw)
}
let loop() =
async {
let! c = acceptClientAsync listener
let sr = new StreamReader(c.GetStream())
let sw = new StreamWriter(c.GetStream())
sw.AutoFlush <- true
do! handleClient c sr sw |> Async.StartChild |> Async.Ignore
return! loop()
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.