5

golang TCPConn.Writeによって返されたエラーを調べて、送信の失敗を検出しようとしていますが、ゼロです。TCPConn.SetWriteDeadlineも使用してみましたが、成功しませんでした。

それが物事が起こる方法です:

  1. サーバーが起動します
  2. クライアントが接続します
  3. サーバーがメッセージを送信し、クライアントがそれを受信する
  4. クライアントがシャットダウンします
  5. サーバーはもう 1 つのメッセージを送信します: エラーなし
  6. サーバーは 3 番目のメッセージを送信します。エラーが表示されるのは今だけです。

質問: 存在しないクライアントへの 2 番目のメッセージだけがエラーになるのはなぜですか? ケースはどのように適切に処理されるべきですか?

コードは次のとおりです。

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

1 に答える 1

12

これは Go 固有のものではなく、基礎となる TCP ソケットが透けて見えることによる成果物です。

TCP 終了手順の適切な図は、このページの下部にあります: http://www.tcpipguide.com/free/t_TCPConnectionTermination-2.htm

単純なバージョンでは、クライアントがソケットを閉じると、FIN が送信され、サーバーから ACK が受信されます。次に、サーバーが同じことを行うのを待ちます。ただし、FIN を送信する代わりに、より多くのデータを送信していますが、これは破棄されます。クライアント ソケットは、あなたからのこれ以上のデータは無効であると想定するため、次に送信するときに RST を取得します。これがバブルアップします。あなたが見るエラーに。

プログラムに戻ると、これを何らかの方法で処理する必要があります。一般に、送信の開始を担当し、終了の開始も担当している人は誰でも考えることができます。したがって、サーバーは、接続を閉じるかエラーが発生するまで送信を続行できると想定する必要があります。クライアントの終了をより確実に検出する必要がある場合は、プロトコルに何らかのクライアント応答が必要です。そうすれば、ソケットで recv を呼び出して 0 を返すことができます。これにより、接続が閉じられていることが警告されます。

Goでは、これは接続の Read メソッドから(またはあなたの場合はコピー内から)EOFエラーを返します。SetWriteDeadline は機能しません。小さな書き込みが行われ、サイレントにドロップされるか、クライアントが最終的に RST で応答してエラーが発生するためです。

于 2013-02-25T16:26:12.907 に答える