2013-02-25 9 views
5

Sto provando a rilevare errori di invio controllando l'errore restituito da golang TCPConn.Write, ma è nullo. Ho anche provato a utilizzare TCPConn.SetWriteDeadline senza successo.golang TCPConn.SetWriteDeadline non sembra funzionare come previsto

Ecco come le cose accadono:

  1. il server avvia
  2. un client si connette
  3. il server invia un messaggio e il cliente riceve lo
  4. il cliente si spegne
  5. la server invia un altro messaggio: nessun errore
  6. il server invia il terzo messaggio ge: solo ora appare l'errore

Domanda: perché solo il secondo messaggio ad un risultato client non-esistenti in un errore? Come dovrebbe essere gestito correttamente il caso?

Il codice segue:

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

Il server è simile al seguente:

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 

Il cliente si presenta così:

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) 

} 

EDIT: Seguendo il suggerimento di JimB I è venuto con un esempio funzionante. I messaggi non si perdono più e vengono reinviati in una nuova connessione. Non sono abbastanza sicuro di quanto sia sicuro utilizzare una variabile condivisa (connWrap.IsFaulted) tra diverse routine di go.

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

È preferibile rispondere alla propria domanda anziché modificare la domanda per includere la risposta – Joakim

risposta

9

Questo non è specifico per Go, ed è un artefatto del socket TCP sottostante che mostra attraverso.

Un diagramma decente dei passaggi di terminazione TCP è in fondo a questa pagina: http://www.tcpipguide.com/free/t_TCPConnectionTermination-2.htm

La versione più semplice è che quando il client chiude la sua presa, invia un FIN, e riceve un ACK dal server . Quindi attende che il server faccia lo stesso. Invece di inviare una FIN, stai inviando più dati, che vengono scartati e il socket del client ora presuppone che altri dati provenienti da te non siano validi, quindi la prossima volta che invii ottieni un RST, che è ciò che bolle nell'errore che vedi.

Tornando al programma, è necessario gestirlo in qualche modo. Generalmente si può pensare a chi è responsabile dell'avvio di una mandata, è inoltre responsabile dell'avvio della risoluzione, quindi il server dovrebbe presumere che possa continuare a inviare fino al che la connessione venga chiusa o che si verifichi un errore. Se è necessario rilevare in modo più affidabile la chiusura del client, è necessario disporre di una sorta di risposta client nel protocollo. In questo modo recv può essere chiamato sul socket e restituire 0, che ti avvisa della connessione chiusa.

In go, questo restituirà un errore EOF dal metodo di lettura della connessione (o dall'interno della copia nel tuo caso).SetWriteDeadline non funziona perché una piccola scrittura andrà comunque e verrà rilasciata silenziosamente, o il client alla fine risponderà con un RST, dandoti un errore.

+0

Grazie per il tuo commento, ho un esempio funzionante ora. –

Problemi correlati