Как в Go работать с TCP/UDP сокетами? Расскажите об основных пакетах и приведите пример простого TCP-сервера и клиента.

Ответ

Да, я работал с низкоуровневыми сетевыми протоколами в Go. Основным инструментом для этого является стандартный пакет net.

Пакет net предоставляет абстракции для работы с TCP и UDP сокетами, позволяя создавать как серверные, так и клиентские приложения.

  • TCP (Transmission Control Protocol): Протокол с установкой соединения, гарантирующий доставку и порядок пакетов. Используется для большинства веб-сервисов (HTTP, FTP), баз данных и т.д.
  • UDP (User Datagram Protocol): Протокол без установки соединения, не гарантирует доставку и порядок. Используется там, где важна скорость, а не надежность (DNS, VoIP, онлайн-игры).

Пример TCP-сервера

Сервер слушает входящие соединения на определенном порту. Каждое новое соединение обрабатывается в отдельной горутине для обеспечения конкурентности.

func handleConnection(conn net.Conn) {
    defer conn.Close()
    fmt.Printf("New connection from %sn", conn.RemoteAddr())

    // Буфер для чтения данных
    buffer := make([]byte, 1024)
    for {
        n, err := conn.Read(buffer)
        if err != nil {
            if err != io.EOF {
                fmt.Println("Read error:", err)
            }
            break // Клиент закрыл соединение
        }
        fmt.Printf("Received: %s", string(buffer[:n]))

        // Отправляем ответ (эхо)
        conn.Write([]byte("Message received.n"))
    }
    fmt.Printf("Connection from %s closedn", conn.RemoteAddr())
}

func main() {
    // Начинаем слушать порт 8080 по протоколу TCP
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal("Listen error:", err)
    }
    defer listener.Close()
    fmt.Println("Server is listening on port 8080")

    for {
        // Ожидаем нового подключения
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Accept error:", err)
            continue
        }
        // Запускаем обработчик в новой горутине
        go handleConnection(conn)
    }
}

Пример TCP-клиента

Клиент устанавливает соединение с сервером и отправляет ему данные.

func main() {
    // Устанавливаем соединение с сервером
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        log.Fatal("Dial error:", err)
    }
    defer conn.Close()

    // Отправляем сообщение
    _, err = conn.Write([]byte("Hello from client!"))
    if err != nil {
        log.Println("Write error:", err)
    }

    // Читаем ответ от сервера
    buffer := make([]byte, 1024)
    n, err := conn.Read(buffer)
    if err != nil {
        log.Println("Read error:", err)
    }
    fmt.Printf("Server response: %s", string(buffer[:n]))
}

Ключевые аспекты при работе с сокетами:

  1. Управление соединениями: Всегда закрывайте соединение с помощью defer conn.Close().
  2. Обработка ошибок: conn.Read() возвращает io.EOF, когда клиент закрывает соединение. Это штатная ситуация, а не ошибка.
  3. Таймауты: Для предотвращения "зависания" на операциях чтения/записи используются таймауты: conn.SetDeadline(), conn.SetReadDeadline(), conn.SetWriteDeadline().