服务器和客户端

时间:2018-03-31 22:59:56

标签: go networking server client deadlock

我有一个测试功能,它既可以创建一个服务器,也可以生成一个充当客户端的goroutine。现在,只需从客户端向服务器发送消息即可,但如果我想创建一个交换,它们似乎会死锁,因为测试永远不会运行完成(如果没有设置r / w截止日期)。例如,我希望客户端向服务器发送消息,服务器复制该消息并将其发送回客户端,然后客户端验证收到的消息是否相同。这是我的测试代码:

func TestSendAwait(t *testing.T) {
    m := "Hello World"

    go func() {
        conn, err := net.Dial("tcp", testingAddr)
        if err != nil {
            t.Fatal(err)
        }
        defer conn.Close()
        t.Log("client connected to server") // DEBUG

        conn.SetDeadline(time.Now().Add(2 * time.Second))
        conn.Write([]byte(m))

        conn.SetDeadline(time.Now().Add(2 * time.Second))
        buf, err := ioutil.ReadAll(conn)
        if err != nil {
            t.Fatal(err)
        }
        t.Log(string(buf))
    }()

    ln, err := net.Listen("tcp", testingAddr)
    if err != nil {
        t.Fatal(err)
    }
    defer ln.Close()
    t.Log("server started") // DEBUG

    conn, err := ln.Accept()
    if err != nil {
        t.Fatal(err)
    }
    defer conn.Close()
    t.Log("server received connection") // DEBUG

    buf, err := ioutil.ReadAll(conn)
    if err != nil {
        t.Fatal(err)
    }
    t.Logf("server read buffer: %v", buf) // DEBUG

    _, err = conn.Write(buf)
    if err != nil {
        t.Fatal(err)
    }
    t.Log("server wrote to connection") // DEBUG
}

在连接上设置截止日期,因为否则死锁将是无限期的。输出如下:

    transmission_test.go:42: server started
    transmission_test.go:24: client connected to server
    transmission_test.go:49: server received connection
    transmission_test.go:32: read tcp 127.0.0.1:41164->127.0.0.1:9090: i/o timeout
    transmission_test.go:55: server read buffer: [72 101 108 108 111 32 87 111 114 108 100]
    transmission_test.go:61: server wrote to connection

Process finished with exit code 1

我不明白为什么客户端无法读取和退出,只有服务器决定从套接字发送数据?即使我增加了客户端的读取截止日期,也会发生这种情况。

1 个答案:

答案 0 :(得分:1)

程序会阻止对ioutil.ReadAll的调用。此函数读取,直到返回io.EOF或其他一些错误。

一种解决方法是在将数据写入连接后关闭写入。这将导致读取对等体返回io.EOF并使ioutil.ReadAll成功返回。

    conn.Write(data)
    cw, ok := conn.(interface{ CloseWrite() error })
    if !ok {
        // handle error
    }
    cw.CloseWrite()

playground example

问题中的程序不保证在拨打连接之前打开侦听器,或者客户端将打印打印收到的消息。操场示例纠正了这些问题。

另一种方法是以某种方式构建消息:

  • 在消息后的消息中写入换行符或其他字节序列。读取直到找到此字节序列。
  • 在消息之前写入消息长度。读取长度,然后指定指定的字节数。