Принять постоянное tcp-соединение на сервере Golang

#go #tcp #buffer #telnet

#Вперед #tcp #буфер #telnet

Вопрос:

Я экспериментирую с Go — и хотел бы создать TCP-сервер, к которому я могу подключаться по telnet, отправлять команды и получать ответы.

 const (
    CONN_HOST = "localhost"
    CONN_PORT = "3333"
    CONN_TYPE = "tcp"
)

func main() {

    listener, err := net.Listen(CONN_TYPE, fmt.Sprintf("%s:%s", CONN_HOST, CONN_PORT))
    if err != nil {
        log.Panicln(err)
    }

    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Panicln(err)
        }

        go handleRequest(conn)
    }
}

func handleRequest(conn net.Conn) {
    buffer := make([]byte, 1024)

    length, err := conn.Read(buffer)
    if err != nil {
        log.Panicln(err)
    }

    str := string(buffer[:length])

    fmt.Println(conn.RemoteAddr().String())
    fmt.Printf("Received command %dt:%sn", length, str)

    switch str {
        case "PINGrn":
        sendResponse("PONG", conn)
        case "PUSHrn":
        sendResponse("GOT PUSH", conn)
    default:
        conn.Write([]byte(fmt.Sprintf("UNKNOWN_COMMAND: %sn", str)))
    }

    conn.Close() // closes the connection
}

func sendResponse(res string, conn net.Conn) {
    conn.Write([]byte(res "n"))
}
  

Приведенный выше фрагмент будет закрывать соединение каждый раз, выгоняя меня из сеанса терминала. Но чего я на самом деле хочу, так это иметь возможность поддерживать соединение открытым для большего количества операций ввода-вывода. Если я просто удалю conn.Close() , то сервер, похоже, где-то зависнет, поскольку он больше не получает никаких ответов.

Способ, которым я решил эту проблему, заключается в том, чтобы мой метод handleRequest бесконечно зацикливался, чтобы он никогда не завершался, пока не получит QUITrn сообщение. Уместно ли это — или есть лучший способ достижения?

 func handleRequest(conn net.Conn) {
    for {
        log.Println("Handling Request")
        buffer := make([]byte, 1024)

        length, err := conn.Read(buffer)
        if err != nil {
            log.Panicln(err)
        }

        str := string(buffer[:length])

        fmt.Println(conn.RemoteAddr().String())
        fmt.Printf("Received command %dt:%sn", length, str)

        switch str {
        case "PINGrn":
            sendResponse("PONG", conn)
        case "PUSHrn":
            sendResponse("GOT PUSH", conn)
        case "QUITrn":
            sendResponse("Goodbye", conn)
            conn.Close()
        default:
            conn.Write([]byte(fmt.Sprintf("UNKNOWN_COMMAND: %sn", str)))
        }
    }
}
  

Комментарии:

1. Да, вам нужно выполнить цикл чтения для чтения более одного раза. Не паникуйте из-за ошибок, нет причин для сбоя вашей программы, если соединение закрывается.

2. Я думаю, что это правильная реализация. Просто подсказка: после того, как вы вызвали conn.Close() не забудьте вызвать оператор break, иначе цикл for будет выполняться вечно

3. Спасибо JimB. Снова паниковать из-за ошибок — я просто делал это в dev, поскольку я еще не создал надлежащий обработчик ошибок. И просто хотел зафиксировать ошибку. Tinwor — хорошее место.

Ответ №1:

Ваш второй пример с циклом — это уже то, что вы хотите. Вы просто выполняете цикл и читаете столько, сколько хотите (или, возможно, до некоторого тайм-аута чтения / записи или внешнего сигнала отмены).

Однако в нем по-прежнему присутствует ошибка: TCP выдает вам поток байтов, где не гарантируется, что одна запись с одной стороны приведет к ровно одному чтению с другой стороны с той же длиной данных. Это означает, что если клиент записывает, PINGrn вы все равно можете получить только PI при первом чтении. Вы могли бы исправить это с помощью bufio.Scanner и всегда читать до первой новой строки.

Ответ №2:

Не уверен, что это то, что вы ищете. Взято из net/http реализации, обертывающей ваш net.TCPListener Accept метод.

tcpKeepAliveListener{listener.(*net.TCPListener)}

 type tcpKeepAliveListener struct {
    *net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
    tc, err := ln.AcceptTCP()
    if err != nil {
        return
    }
    tc.SetKeepAlive(true)
    tc.SetKeepAlivePeriod(3 * time.Minute)
    return tc, nil
}
  

Смотрите: Ссылка 1 и ссылка 2

Комментарии:

1. Не совсем то, что я хотел, но очень полезно. Спасибо