【问题标题】:what's wrong on this f# echo server?这个 f# echo 服务器出了什么问题?
【发布时间】:2016-12-06 01:05:41
【问题描述】:

我已经编写了这个 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. 当客户端说“退出”时,关闭连接

但是当我运行程序时,当客户端发送“左”、“右”和“退出”时,我得到了这个异常:

未处理的异常:System.ObjectDisposedException:(不写 关闭)TextWriter。在 Microsoft.FSharp.Control.CancellationTokenOps.Start@1192-1.Invoke(异常 e)在.$Control.loop@419-40(蹦床这个, FSharpFunc2 action) in Microsoft.FSharp.Control.Trampoline.ExecuteAction(FSharpFunc2 第一个动作)在 Microsoft.FSharp.Control.TrampolineHolder.Protect(FSharpFunc`2 第一个动作)在 .$Control.-ctor@476-1.Invoke(Object state) in System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(对象 状态)在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext、ContextCallback 回调、对象状态、 BooleanpreserveSyncCtx) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext、ContextCallback 回调、对象状态、布尔值 preserveSyncCtx) 在 System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 在 System.Threading.ThreadPoolWorkQueue.Dispatch() 中 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() 。 . .(按任意键继续)

Screenshot of program in action
Screenshot of exception

我该如何解决这个问题?

【问题讨论】:

  • 你得到什么异常?您可以将该信息添加到您的问题中吗?
  • 请将异常复制并粘贴到问题中,而不是使用屏幕截图。异常的屏幕截图不允许任何人通过 Google 搜索异常文本,因此它的帮助较小。另外,请让我们查看异常的整个堆栈跟踪,而不仅仅是第一行。查看堆栈跟踪的其余部分可能会提供一些线索,让人们找出导致异常的原因。

标签: f#


【解决方案1】:

问题在于,与您对命令式语言中的同名语言所期望的不同,计算表达式中的return 不会短路。因此,一旦if line.Equals("right") 中的return! 返回,即。套接字关闭后,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)
}

【讨论】:

    【解决方案2】:

    你的代码有问题:

    if line.Equals("left") then
        sw.WriteLine("right")
        return! loop2(c,sr,sw)
    sw.WriteLine(line)
    return! loop2(c,sr,sw)
    

    如果收到“left”,则写入“right”,然后执行嵌套的loop2s,直到收到“quit”。然后,在所有这些都完成之后,它会尝试写入line 并执行更多嵌套的loop2s。当然,此时,您已经处理掉了连接,因此出现了异常。

    似乎写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&lt;'T&gt; 编写的,而不是 F# 特有的 async&lt;'T&gt;,所以我建议创建一些辅助函数:

    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()
        }
    

    【讨论】:

      猜你喜欢
      • 2022-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-07
      相关资源
      最近更新 更多