繁体   English   中英

这个f#echo服务器出了什么问题?

[英]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())

这个程序可以做到:

  1. 回应客户的消息
  2. 当客户说“离开”时,返回“正确”
  3. 当客户说'退出'时,关闭连接

但是当我运行编程时,当客户端发送'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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM