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

时间:2016-12-06 01:05:41

标签: f#

我已经写了这个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. 当客户说&#39;离开&#39;,返回&#39;对&#39;
  3. 当客户说&#39;退出&#39;时,关闭连接
  4. 但是当我运行编程时,当客户端发送&#39;离开&#39;正确&#39;并且发送&#39;退出&#39;时,我得到了这个例外:< / p>

      

    未处理异常:System.ObjectDisposedException :(不要写   关闭)TextWriter。in   Microsoft.FSharp.Control.CancellationTokenOps.Start@1192-1.Invoke(例外   e)in。$ Control.loop @ 419-40(Trampoline this,   FSharpFunc 2 action) in Microsoft.FSharp.Control.Trampoline.ExecuteAction(FSharpFunc 2   firstAction)in   Microsoft.FSharp.Control.TrampolineHolder.Protect(FSharpFunc`2   firstAction)in   。$ Control.-ctor @ 476-1.Invoke(对象状态)in   System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(对象   国家)   System.Threading.ExecutionContext.RunInternal(执行上下文   executionContext,ContextCallback回调,对象状态,   BooleanpreserveSyncCtx)in   System.Threading.ExecutionContext.Run(执行上下文   executionContext,ContextCallback回调,对象状态,布尔值   preserveSyncCtx)in   System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()   在System.Threading.ThreadPoolWorkQueue.Dispatch()中   System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()。 。   。(按任意键继续)

    Screenshot of program in action
    Screenshot of exception

    我该如何解决这个问题?

2 个答案:

答案 0 :(得分:2)

问题在于,与命令式语言中的同名不同,计算表达式中的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) }

(echo dataExample) >> C:\filename.txt 2> nul

答案 1 :(得分:2)

您的代码中存在问题:

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

如果&#34;离开&#34;收到,它写道&#34;对&#34;然后执行嵌套的loop2直到&#34;退出&#34;收到了。然后,在完成所有这些操作之后,它会尝试编写line并执行更多嵌套的loop2。当然,到目前为止,您已经处理了连接,因此例外。

似乎写line应该在else块中,这样可以防止错误:

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

当然,您也可以将此检查与模式匹配集成。下面的示例将在一个结构中处理空检查和每个字符串选项。

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