2013-02-25 39 views
5

我試圖通過檢查由golang TCPConn.Write返回的錯誤來檢測發送失敗,但它是零。我也嘗試使用TCPConn.SetWriteDeadline沒有成功。golang TCPConn.SetWriteDeadline似乎沒有按預期工作

這是如何的事情發生了:

  1. 服務器啓動
  2. 客戶端連接
  3. 服務器發送一條消息,客戶端接收它
  4. 客戶關閉
  5. 的服務器再發送一條消息:無錯誤
  6. 服務器發送第三條消息GE:現在纔出現的錯誤

問題:爲什麼只有第二個消息中的錯誤不存在的客戶端的結果嗎?應如何妥善處理案件?

的代碼如下:

package main 

import (
    "net" 
    "os" 
    "bufio" 
    "fmt" 
    "time" 
) 

func AcceptConnections(listener net.Listener, console <- chan string) { 

    msg := "" 

    for { 

     conn, err := listener.Accept() 

     if err != nil { 
      panic(err) 
     } 

     fmt.Printf("client connected\n") 

     for { 

      if msg == "" { 
       msg = <- console 
       fmt.Printf("read from console: %s", msg) 
      } 

      err = conn.SetWriteDeadline(time.Now().Add(time.Second)) 

      if err != nil { 
       fmt.Printf("SetWriteDeadline failed: %v\n", err) 
      } 

      _, err = conn.Write([]byte(msg)) 

      if err != nil { 
       // expecting an error after sending a message 
       // to a non-existing client endpoint 
       fmt.Printf("failed sending a message to network: %v\n", err) 
       break 
      } else { 
       fmt.Printf("msg sent: %s", msg) 
       msg = "" 
      } 
     } 
    } 
} 

func ReadConsole(network chan <- string) { 

    console := bufio.NewReader(os.Stdin) 

    for { 

     line, err := console.ReadString('\n') 

     if err != nil { 

      panic(err) 

     } else { 

      network <- line 
     } 
    } 
} 

func main() { 

    listener, err := net.Listen("tcp", "localhost:6666") 

    if err != nil { 
     panic(err) 
    } 

    println("listening on " + listener.Addr().String()) 

    consoleToNetwork := make(chan string) 

    go AcceptConnections(listener, consoleToNetwork) 

    ReadConsole(consoleToNetwork) 
} 

服務器控制檯看起來是這樣的:

listening on 127.0.0.1:6666 
client connected 
hi there! 
read from console: hi there! 
msg sent: hi there! 
this one should fail 
read from console: this one should fail 
msg sent: this one should fail 
this one actually fails 
read from console: this one actually fails 
failed sending a message to network: write tcp 127.0.0.1:51194: broken pipe 

客戶看起來是這樣的:

package main 

import (
    "net" 
    "os" 
    "io" 
    //"bufio" 
    //"fmt" 
) 

func cp(dst io.Writer, src io.Reader, errc chan<- error) { 

    // -reads from src and writes to dst 
    // -blocks until EOF 
    // -EOF is not an error 
    _, err := io.Copy(dst, src) 

    // push err to the channel when io.Copy returns 
    errc <- err 
} 

func StartCommunication(conn net.Conn) { 

    //create a channel for errors 
    errc := make(chan error) 

    //read connection and print to console 
    go cp(os.Stdout, conn, errc) 

    //read user input and write to connection 
    go cp(conn, os.Stdin, errc) 

    //wait until nil or an error arrives 
    err := <- errc 

    if err != nil { 
     println("cp error: ", err.Error()) 
    } 
} 

func main() { 

    servAddr := "localhost:6666" 

    tcpAddr, err := net.ResolveTCPAddr("tcp", servAddr) 

    if err != nil { 
     println("ResolveTCPAddr failed:", err.Error()) 
     os.Exit(1) 
    } 

    conn, err := net.DialTCP("tcp", nil, tcpAddr) 

    if err != nil { 
     println("net.DialTCP failed:", err.Error()) 
     os.Exit(1) 
    } 

    defer conn.Close() 

    StartCommunication(conn) 

} 

編輯:繼JimB的建議,我想出了一個工作的例子。消息不會再丟失,並以新的連接重新發送。我不太清楚在不同的go例程之間使用共享變量(connWrap.IsFaulted)有多安全。

package main 

import (
    "net" 
    "os" 
    "bufio" 
    "fmt" 
) 

type Connection struct { 
    IsFaulted bool 
    Conn net.Conn 
} 

func StartWritingToNetwork(connWrap * Connection, errChannel chan <- error, msgStack chan string) { 

    for { 

     msg := <- msgStack 

     if connWrap.IsFaulted { 

      //put it back for another connection 
      msgStack <- msg 

      return 
     } 

     _, err := connWrap.Conn.Write([]byte(msg)) 

     if err != nil { 

      fmt.Printf("failed sending a message to network: %v\n", err) 

      connWrap.IsFaulted = true 

      msgStack <- msg 

      errChannel <- err 

      return 

     } else { 

      fmt.Printf("msg sent: %s", msg) 
     } 
    } 
} 

func StartReadingFromNetwork(connWrap * Connection, errChannel chan <- error){ 

    network := bufio.NewReader(connWrap.Conn) 

    for (!connWrap.IsFaulted) { 

     line, err := network.ReadString('\n') 

     if err != nil { 

      fmt.Printf("failed reading from network: %v\n", err) 

      connWrap.IsFaulted = true 

      errChannel <- err 

     } else { 

      fmt.Printf("%s", line) 
     } 
    } 
} 

func AcceptConnections(listener net.Listener, console chan string) { 

    errChannel := make(chan error) 

    for { 

     conn, err := listener.Accept() 

     if err != nil { 
      panic(err) 
     } 

     fmt.Printf("client connected\n") 

     connWrap := Connection{false, conn} 

     go StartReadingFromNetwork(&connWrap, errChannel) 

     go StartWritingToNetwork(&connWrap, errChannel, console) 

     //block until an error occurs 
     <- errChannel 
    } 
} 

func ReadConsole(network chan <- string) { 

    console := bufio.NewReader(os.Stdin) 

    for { 

     line, err := console.ReadString('\n') 

     if err != nil { 

      panic(err) 

     } else { 

      network <- line 
     } 
    } 
} 

func main() { 

    listener, err := net.Listen("tcp", "localhost:6666") 

    if err != nil { 
     panic(err) 
    } 

    println("listening on " + listener.Addr().String()) 

    consoleToNetwork := make(chan string) 

    go AcceptConnections(listener, consoleToNetwork) 

    ReadConsole(consoleToNetwork) 
} 
+1

最好回答自己的問題,而不是編輯問題以包含答案 – Joakim 2017-10-15 19:53:30

回答

9

這不是特定的,它是底層TCP套接字顯示的工件。

的TCP終止步驟體面圖是在該頁面的底部: http://www.tcpipguide.com/free/t_TCPConnectionTermination-2.htm

的簡單的版本是,當客戶端關閉它的插座,它發送一個FIN,並從服務器接收到ACK 。然後等待服務器執行相同的操作。不是發送FIN,而是發送更多的數據,這些數據會被丟棄,而客戶端套接字現在假定來自您的更多數據是無效的,因此下次您發送RST時,會發生什麼進入你看到的錯誤。

回到你的程序,你需要以某種方式處理這個問題。一般情況下,您可以考慮負責發起發送的人,也負責發起終止,因此您的服務器應該假設它可以繼續發送,直至關閉連接或遇到錯誤。如果您需要更可靠地檢測客戶端關閉,您需要在協議中有某種客戶端響應。這樣recv可以在套接字上調用並返回0,這會提醒你關閉連接。

在go中,這將從連接的Read方法(或從您的案例中的Copy中)返回一個EOF錯誤。SetWriteDeadline不起作用,因爲一個小的寫入將會通過,然後悄悄丟棄,或者客戶端最終會用RST響應,給你一個錯誤。

+0

感謝您的評論,我現在有一個工作示例。 – 2013-02-25 18:11:46