2016-12-06 21 views
3

我寫這個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. 時所說的 '左' 的客戶,回報 '正確'
  3. 當客戶說'退出'時,關閉連接

但是當我運行編程時,當客戶端發送 '左',獲得 '右',並派 '退出',我得到這個異常:

not handled exception: System.ObjectDisposedException: (con't write to closed) TextWriter。 in [email protected]e(Exception e) in [email protected](Trampoline this, FSharpFunc 2 action) in Microsoft.FSharp.Control.Trampoline.ExecuteAction(FSharpFunc 2 firstAction) in Microsoft.FSharp.Control.TrampolineHolder.Protect(FSharpFunc`2 firstAction) in [email protected](Object state) in System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state) in System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, BooleanpreserveSyncCtx) in System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) in System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() in System.Threading.ThreadPoolWorkQueue.Dispatch() in System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() . . .(press any key to continue)

Screenshot of program in action
Screenshot of exception

如何解決這個問題?

+2

你會得到什麼例外?你能否將這些信息添加到你的問題中。 – s952163

+3

請將問題複製並粘貼到問題中,而不是使用屏幕截圖。例外情況的屏幕截圖不允許任何人Google搜索例外文本,所以它的幫助不大。另外,請讓我們看看異常的整個堆棧跟蹤,而不是僅僅看第一行。看到堆棧跟蹤的其餘部分可能會提供一些線索,讓別人找出導致異常的原因。 – rmunn

回答

2

問題在於,與計算表達式中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) 

如果「左」被接收,它寫道:「正確的」,然後直到「跳槽」收到執行嵌套loop2秒。然後,在所有這些完成後,它會嘗試寫入line並執行更多嵌套的loop2 s。當然,由於這一點,你已經處理了連接,因此是例外。

好像寫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() 
    }