Как управлять соединениями, возвращаемыми Accept () в golang? - PullRequest
0 голосов
/ 20 января 2020

Для tcp-сервера мы предлагаем Listen () и Accept ().

func main() {
    ln, err := net.Listen("tcp", ":6000")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    for {
        conn, err := ln.Accept()
        if err != nil {
            fmt.Println(err)
            continue
        }

        go handleConnection(conn)
    }
}

Но есть два способа обработки соединения, возвращаемого Accept:

1

func handleConnection(c net.Conn) {
    buf := make([]byte, 4096)

    for {
        n, err := c.Read(buf)
        if err != nil || n == 0 {
            c.Close()
            break
        }
        n, err = c.Write(buf[0:n])
        if err != nil {
            c.Close()
            break
        }
    }
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

2

func handleConnection(c net.Conn) {
    defer c.Close()
    buf := make([]byte, 4096)   

    n, err := c.Read(buf)
    if err != nil || n == 0 {
        c.Close()
        break
    }
    n, err = c.Write(buf[0:n])
    if err != nil {
        c.Close()
        break
    }
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

Есть эти 2 подхода верны? Похоже, что подход 1 повторно использует соединения. Но как указать клиенту EOF?

Ответы [ 2 ]

0 голосов
/ 20 января 2020

Этот ответ предполагает, что цель программы - передать данные клиенту. Этот ответ включает в себя информацию в комментариях от torek и limon.

Документация для Read говорит

Read читает до байтов len (p) в p. Он возвращает количество прочитанных байтов (0 <= n <= len (p)) и обнаруженную ошибку. Даже если Read возвращает n <len (p), он может использовать все p как пустое место во время вызова. Если некоторые данные доступны, но не len (p) байтов, Read обычно возвращает то, что доступно, вместо ожидания большего. </p>

Вторая программа может не прочитать все данные, потому что Read может вернуть доступные данные вместо того, чтобы ждать большего. Вторая программа может не записать все данные, потому что чтение может вернуть данные и ошибку.

Давайте сосредоточимся на первой программе, потому что она более корректна. Программа может выполнять несколько вызовов Read и Write для одного соединения, но это не означает повторное использование соединения.

Лучше defer c.Close() в начале функции, чем разбрасывать вызовы до c.Close() по всей функции. Отсрочка обеспечивает закрытие соединения независимо от выхода из функции.

func handleConnection(c net.Conn) {
    defer c.Close()
    buf := make([]byte, 4096)

    for {
        n, err := c.Read(buf)
        if err != nil || n == 0 {
            break
        }
        n, err = c.Write(buf[0:n])
        if err != nil {
            break
        }
    }
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

Read может вернуть некоторые данные, а также ошибку. Обработайте любые данные, возвращенные до обработки ошибки:

func handleConnection(c net.Conn) {
    defer c.Close()
    buf := make([]byte, 4096)

    for {
        n, er := c.Read(buf)
        if n > 0 {
            _, ew := c.Write(buf[:n])
            if ew != nil {
                break
            }
        }
        if er != nil {
            break
        }
    }
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

Используйте io.Copy вместо записи копии logi c в приложении. Функция io.Copy дает все детали правильно. Кроме того, io.Copy может использовать некоторые низкоуровневые оптимизации для копирования данных из одного соединения в другое.

func handleConnection(c net.Conn) {
    defer c.Close()
    io.Copy(c, c)
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}
0 голосов
/ 20 января 2020

Два подхода делают разные вещи:

Первый подход считывает некоторые данные и записывает их обратно до ошибки или до тех пор, пока не будет прочитано 0 байт.

Второй подход читает один раз, пишет что обратно, а затем закрывает соединение.

Первый подход не «повторно» использовать соединение. Он реализует простой эхо-подобный протокол, который работает до тех пор, пока соединение не будет разорвано. Второй подход повторяется один раз и закрывает соединение.

...