Необычно большое количество ошибок тайм-аута соединения TCP - PullRequest
0 голосов
/ 26 марта 2019

Я использую Go TCP Client для подключения к нашему Go TCP Server.

Я могу подключиться к серверу и правильно выполнить команды, но время от времени мой TCP-клиент сообщает о необычно большом количестве последовательных ошибок подключения TCP при попытке подключения к нашему TCP-серверу или при отправке сообщения. после подключения:

dial tcp kubernetes_node_ip:exposed_kubernetes_port:
connectex: A connection attempt failed because the connected party did not properly
respond after a period of time, or established connection failed because connected
host has failed to respond.

read tcp unfamiliar_ip:unfamiliar_port->kubernetes_node_ip:exposed_kubernetes_port
wsarecv: A connection attempt failed because the connected party did not properly
respond after a period of time, or established connection failed because connected
host has failed to respond.

Я говорю «необычно высокий», потому что я предполагаю, что число раз, когда эти ошибки происходят, должно быть очень минимальным (около 5 или меньше в течение часа). Обратите внимание, что я не исключаю, что это может быть вызвано нестабильностью соединения, так как я также заметил, что можно выполнять несколько команд подряд без каких-либо ошибок.

Однако я все еще собираюсь опубликовать свой код на случай, если я что-то не так делаю.

Ниже приведен код, который мой TCP-клиент использует для подключения к нашему серверу:

serverAddress, err := net.ResolveTCPAddr("tcp", kubernetes_ip+":"+kubernetes_port)
if err != nil {     
    fmt.Println(err)
    return
}

// Never stop asking for commands from the user.
for {
    // Connect to the server.
    serverConnection, err := net.DialTCP("tcp", nil, serverAddress)
    if err != nil {         
        fmt.Println(err)
        continue
    }

    defer serverConnection.Close()

    // Added to prevent connection timeout errors, but doesn't seem to be helping
    // because said errors happen within just 1 or 2 minutes.
    err = serverConnection.SetDeadline(time.Now().Add(10 * time.Minute))
    if err != nil {         
        fmt.Println(err)
        continue
    }

    // Ask for a command from the user and convert to JSON bytes...

    // Send message to server.
    _, err = serverConnection.Write(clientMsgBytes)
    if err != nil {
        err = merry.Wrap(err)
        fmt.Println(merry.Details(err))
        continue
    }

    err = serverConnection.CloseWrite()
    if err != nil {
        err = merry.Wrap(err)
        fmt.Println(merry.Details(err))
        continue
    }

    // Wait for a response from the server and print...
}

Ниже приведен код, который использует наш TCP-сервер для приема клиентских запросов:

// We only supply the port so the IP can be dynamically assigned:
serverAddress, err := net.ResolveTCPAddr("tcp", ":"+server_port)
if err != nil {     
    return err
}

tcpListener, err := net.ListenTCP("tcp", serverAddress)
if err != nil {     
    return err
}

defer tcpListener.Close()

// Never stop listening for client requests.
for {
    clientConnection, err := tcpListener.AcceptTCP()
    if err != nil {         
        fmt.Println(err)
        continue
    }

    go func() {
        // Add client connection to Job Queue.
        // Note that `clientConnections` is a buffered channel with a size of 1500.
        // Since I am the only user connecting to our server right now, I do not think
        // this is a channel blocking issue.
        clientConnections <- clientConnection
    }()
}

Ниже приведен код, который использует наш TCP-сервер для обработки клиентских запросов:

defer clientConnection.Close()

// Added to prevent connection timeout errors, but doesn't seem to be helping
// because said errors happen within just 1 or 2 minutes.
err := clientConnection.SetDeadline(time.Now().Add(10 * time.Minute))
if err != nil {     
    return err
}

// Read full TCP message.
// Does not stop until an EOF is reported by `CloseWrite()`
clientMsgBytes, err := ioutil.ReadAll(clientConnection)
if err != nil {
    err = merry.Wrap(err)
    return nil, err
}

// Process the message bytes...

Мои вопросы:

  1. Я что-то не так делаю в вышеприведенном коде или он достаточно приличный для базовых операций клиент-сервер TCP?

  2. Это нормально, что и у клиента TCP, и у сервера TCP есть код, который откладывает закрытие одного соединения?

  3. Кажется, я помню, что вызов defer внутри цикла ничего не делает. Как правильно закрыть клиентские подключения перед запуском новых?

Некоторая дополнительная информация:

  • Указанные ошибки не регистрируются TCP-сервером, кроме нестабильности соединения, это также может быть Проблема, связанная с Kubernetes / Docker.

1 Ответ

1 голос
/ 26 марта 2019

Кажется, этот кусок кода не действует так, как вы думаете. Оператор defer для закрытия соединения произойдет только тогда, когда функция вернется, а не когда итерация закончится. Итак, насколько я вижу здесь, вы создаете много соединений на стороне клиента, это может быть проблемой.

serverAddress, err := net.ResolveTCPAddr("tcp", kubernetes_ip+":"+kubernetes_port)
if err != nil {     
    fmt.Println(err)
    return
}

// Never stop asking for commands from the user.
for {
    // Connect to the server.
    serverConnection, err := net.DialTCP("tcp", nil, serverAddress)
    if err != nil {         
        fmt.Println(err)
        continue
    }

    defer serverConnection.Close()

    // Added to prevent connection timeout errors, but doesn't seem to be helping
    // because said errors happen within just 1 or 2 minutes.
    err = serverConnection.SetDeadline(time.Now().Add(10 * time.Minute))
    if err != nil {         
        fmt.Println(err)
        continue
    }

    // Ask for a command from the user and send to the server...

    // Wait for a response from the server and print...
}

Предлагаю написать так:

func start() {
    serverAddress, err := net.ResolveTCPAddr("tcp", kubernetes_ip+":"+kubernetes_port)
    if err != nil {     
        fmt.Println(err)
        return
    }
    for {
        if err := listen(serverAddress); err != nil {
            fmt.Println(err)
        }
    }
}

func listen(serverAddress string) error {
     // Connect to the server.
     serverConnection, err := net.DialTCP("tcp", nil, serverAddress)
     if err != nil {         
         fmt.Println(err)
         continue
     }

    defer serverConnection.Close()

    // Never stop asking for commands from the user.
    for {
        // Added to prevent connection timeout errors, but doesn't seem to be helping
        // because said errors happen within just 1 or 2 minutes.
        err = serverConnection.SetDeadline(time.Now().Add(10 * time.Minute))
        if err != nil {         
           fmt.Println(err)
           return err
        }

        // Ask for a command from the user and send to the server...

        // Wait for a response from the server and print...
    }
}

Кроме того, вы должны держать одно соединение открытым или пул соединений, а не открывать и закрывать соединение сразу. Затем, когда вы отправляете сообщение, вы получаете соединение из пула (или одиночное соединение), вы пишете сообщение и ждете ответа, а затем освобождаете соединение для пула.

Примерно так:

res, err := c.Send([]byte(`my message`))
if err != nil {
    // handle err
}

// the implementation of send
func (c *Client) Send(msg []byte) ([]byte, error) {
    conn, err := c.pool.Get() // returns a connection from the pool or starts a new one
    if err != nil {
        return nil, err
    }
    // send your message and wait for response
    // ...
    return response, nil
}
...