简体   繁体   English

golang TCPConn.SetWriteDeadline似乎没有按预期工作

[英]golang TCPConn.SetWriteDeadline doesn't seem to work as expected

I'm trying to detect sending failures by inspecting the error returned by golang TCPConn.Write , but it's nil. 我试图通过检查golang TCPConn.Write返回的错误来检测发送失败,但它没有。 I also tried using TCPConn.SetWriteDeadline without success. 我也尝试过使用TCPConn.SetWriteDeadline但没有成功。

That's how things happen: 事情就是这样:

  1. the server starts 服务器启动
  2. a client connects 客户连接
  3. the server sends a message and the client receives it 服务器发送消息,客户端接收消息
  4. the client shuts down 客户关闭了
  5. the server sends one more message: no error 服务器再发送一条消息:没有错误
  6. the server sends the third message: only now the error appears 服务器发送第三条消息:只是现在出现错误

Question : why only the second message to a non-existing client results in an error? 问题 :为什么只有第二条消息发送给不存在的客户端会导致错误? How should the case be handled properly? 案件应如何妥善处理?

The code follows: 代码如下:

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

The server console looks like this: 服务器控制台如下所示:

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

The client looks like this: 客户端看起来像这样:

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 : Following JimB's suggestion I came up with a working example. 编辑 :按照JimB的建议,我想出了一个有效的例子。 Messages don't get lost any more and are re-sent in a new connection. 消息不会再丢失,并在新连接中重新发送。 I'm not quite sure though how safe is it to use a shared variable (connWrap.IsFaulted) between different go routines. 我不太确定在不同的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)
}

This isn't Go specific, and is a artifact of the underlying TCP socket showing through. 这不是Go特定的,并且是底层TCP套接字的工件。

A decent diagram of the TCP termination steps is at the bottom of this page: http://www.tcpipguide.com/free/t_TCPConnectionTermination-2.htm TCP终止步骤的合适图表位于本页底部: http//www.tcpipguide.com/free/t_TCPConnectionTermination-2.htm

The simple version is that when the client closes its socket, it sends a FIN, and receives an ACK from the server. 简单的版本是当客户端关闭其套接字时,它发送FIN,并从服务器接收ACK。 It then waits for the server to do the same. 然后它等待服务器执行相同的操作。 Instead of sending a FIN though, you're sending more data, which is discarded, and the client socket now assumes that any more data coming from you is invalid, so the next time you send you get an RST, which is what bubbles up into the error you see. 虽然不是发送FIN,而是发送更多数据,而这些数据被丢弃,而客户端套接字现在假定来自您的更多数据无效,因此下次发送时会获得RST,这就是冒泡你看到的错误。

Going back to your program, you need to handle this somehow. 回到你的程序,你需要以某种方式处理它。 Generally you can think of whomever is in charge of initiating a send, is also in charge of initiating termination, hence your server should assume that it can continue to send until it closes the connection, or encounters an error. 通常,您可以考虑负责启动发送的任何人,也负责启动终止,因此您的服务器应该假定它可以继续发送,直到关闭连接或遇到错误。 If you need to more reliably detect the client closing, you need to have some sort of client response in the protocol. 如果需要更可靠地检测客户端关闭,则需要在协议中进行某种客户端响应。 That way recv can be called on the socket and return 0, which alerts you to the closed connection. 这样可以在套接字上调用recv并返回0,它会提醒您关闭连接。

In go, this will return an EOF error from the connection's Read method (or from within the Copy in your case). 在go中,这将从连接的Read方法(或在您的情况下从Copy中)返回EOF错误。 SetWriteDeadline doesn't work because a small write will go though and get dropped silently, or the client will eventually respond with an RST, giving you an error. SetWriteDeadline不起作用,因为一个小的写入将通过并静默删除,或者客户端最终将使用RST响应,从而导致错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM