鍍金池/ 問答/Java  GO  Linux/ Write on a closed net.Conn but returned

Write on a closed net.Conn but returned nil error

先上一段簡單的代碼:

package main

import (
    "fmt"
    "time"
    "net"
)

func main() {
    addr := "127.0.0.1:8999"
    // Server
    go func() {
        tcpaddr, err := net.ResolveTCPAddr("tcp4", addr)
        if err != nil {
            panic(err)
        }
        listen, err := net.ListenTCP("tcp", tcpaddr)
        if err != nil {
            panic(err)
        }
        for  {
            if conn, err := listen.Accept(); err != nil {
                panic(err)
            } else if conn != nil {
                go func(conn net.Conn) {
                    buffer := make([]byte, 1024)
                    n, err := conn.Read(buffer)
                    if err != nil {
                        fmt.Println(err)
                    } else {
                        fmt.Println(">", string(buffer[0 : n]))
                    }
                    conn.Close()
                }(conn)
            }
        }
    }()
    time.Sleep(time.Second)
    // Client
    if conn, err := net.Dial("tcp", addr); err == nil {
        for i := 0; i < 2; i++ {
            _, err := conn.Write([]byte("hello"))
            if err != nil {
                fmt.Println(err)
                conn.Close()
                break
            } else {
                fmt.Println("ok")
            }
            // sleep 10 seconds and re-send
            time.Sleep(10*time.Second)
        }
    } else {
        panic(err)
    }
}

客戶端往服務端發(fā)送數(shù)據(jù),總共發(fā)送了兩次,第一次過后服務端關(guān)閉了鏈接,過了10秒后客戶端仍然使用同一個conn對象寫入數(shù)據(jù),并且返回Write方法返回成功。

這里舊奇了怪了,明明服務端都關(guān)閉了鏈接,并且都已經(jīng)過了10秒客戶端繼續(xù)往同一個鏈接對象寫數(shù)據(jù),為啥還能成功呢?

有大神能解答一下么,非常感謝。

PS1:

為便于驗證是否是緩沖區(qū)造成的問題,我嘗試過將第二次發(fā)送設置很大的內(nèi)容來進行發(fā)送,結(jié)果依舊是成功的,代碼及截圖如下:

// Client
if conn, err := net.Dial("tcp", addr); err == nil {
    _, err := conn.Write([]byte("hello"))
    if err != nil {
        fmt.Println(err)
        conn.Close()
        return
    } else {
        fmt.Println("ok")
    }
    // sleep 10 seconds and re-send
    time.Sleep(10*time.Second)

    b := make([]byte, 40000)
    for i := range b {
        b[i] = 'x'
    }
    n, err := conn.Write(b)
    if err != nil {
        fmt.Println(err)
        conn.Close()
        return
    } else {
        fmt.Println("ok", n)
    }
    // sleep 10 seconds and re-send
    time.Sleep(10*time.Second)
} else {
    panic(err)
}

圖片描述

PS2:

我用wireshark測試了一下,其實在Client第二次Write的時候,Client的TCP狀態(tài)就已經(jīng)是CLOSE_WAIT了,按理說這個狀態(tài)就表示Server端(處于FIN_WAIT_2狀態(tài))已經(jīng)不接收數(shù)據(jù)了(我試過Server端Close之后再Read操作會報錯),那么這個時候Client再Write其實根本就沒有什么意義了,那為什么Client端的Write還能成功呢?

wireshark:
圖片描述

netstat:
圖片描述

回答
編輯回答
伐木累

你這個寫法都錯誤了,代碼注釋地方。

package main

import (
    "fmt"
    "time"
    "net"
)

func main() {
    addr := "127.0.0.1:8999"
    go func() {
        tcpaddr, err := net.ResolveTCPAddr("tcp4", addr)
        if err != nil {
            panic(err)
        }
        listen, err := net.ListenTCP("tcp", tcpaddr)
        if err != nil {
            panic(err)
        }
        for  {
            if conn, err := listen.Accept(); err != nil {
                panic(err)
            } else if conn != nil {
                go func(conn net.Conn) {
                    buffer := make([]byte, 1024)
                    n, err := conn.Read(buffer)
                    if err != nil {
                        fmt.Println(err)
                    } else {
                        fmt.Println(">", string(buffer[0 : n]))
                    }
                    conn.Close()// 
                }(conn)
            }
        }
    }()
    time.Sleep(time.Second)
    // Client
    if conn, err := net.Dial("tcp", addr); err == nil {
        for i := 0; i < 2; i++ {
            _, err := conn.Write([]byte("hello"))
            if err != nil {
                fmt.Println(err)
                conn.Close()
                break
            } else {
                fmt.Println("ok")
            }
            // sleep 10 seconds and re-send
            time.Sleep(10*time.Second)
        }
    } else {
        panic(err)
    }
}
2017年8月10日 18:37
編輯回答
怣痛

當服務器端執(zhí)行 conn.Close() 后,服務器發(fā)送 FIN TCP 包給客戶端,在客戶端確認之后,該 TCP 連接變成“半開”(half-open)狀態(tài)。

“半開”狀態(tài)意味著服務器不能再向客戶端發(fā)送數(shù)據(jù),同理,客戶端可以發(fā)送數(shù)據(jù)出去,因此 conn.Write() 不會報錯。不過客戶端最后一次發(fā)送的數(shù)據(jù)不會被服務器接收到。

你可以使用 wireshark 抓包分析 TCP 協(xié)議。

參考

[1] https://en.wikipedia.org/wiki...

2017年4月19日 01:49