Расскажите о протоколе TCP и приведите примеры его использования в Go

Ответ

TCP (Transmission Control Protocol) — это один из основных протоколов сетевой модели TCP/IP. Его главная задача — обеспечить надежную, упорядоченную и безошибочную доставку потока данных между двумя программами, запущенными на разных хостах в сети.

Ключевые характеристики TCP:

  • Надежность (Reliability): Гарантирует, что данные будут доставлены. Если пакет теряется, TCP запрашивает его повторную отправку.
  • Установка соединения (Connection-oriented): Перед обменом данными клиенту и серверу необходимо установить соединение через процесс "тройного рукопожатия" (three-way handshake: SYN, SYN-ACK, ACK).
  • Упорядоченная доставка (Ordered Delivery): TCP гарантирует, что данные будут получены в том же порядке, в котором были отправлены.
  • Контроль потока (Flow Control): Механизм, который не позволяет быстрому отправителю перегрузить медленного получателя.

Благодаря этим свойствам, TCP используется в большинстве популярных прикладных протоколов, где важна целостность данных: HTTP/HTTPS, FTP, SMTP, SSH, Telnet.

Примеры использования в Go:

В стандартной библиотеке Go (net) есть все необходимое для работы с TCP.

  1. Простой TCP-сервер Сервер слушает порт, принимает входящие соединения и обрабатывает каждое в отдельной горутине.

    package main
    
    import (
        "fmt"
        "net"
    )
    
    func handleConnection(conn net.Conn) {
        defer conn.Close()
        fmt.Printf("Accepted connection from %sn", conn.RemoteAddr())
        // ... логика чтения/записи данных
        conn.Write([]byte("Hello from server!n"))
    }
    
    func main() {
        // Начинаем слушать порт 8080 по протоколу TCP
        ln, err := net.Listen("tcp", ":8080")
        if err != nil {
            panic(err)
        }
        defer ln.Close()
        fmt.Println("Server is listening on port 8080")
    
        for {
            // Ожидаем и принимаем новое соединение
            conn, err := ln.Accept()
            if err != nil {
                fmt.Println("Error accepting connection:", err)
                continue
            }
            // Обрабатываем соединение в новой горутине
            go handleConnection(conn)
        }
    }
  2. TCP-клиент Клиент устанавливает соединение с сервером, отправляет данные и читает ответ.

    package main
    
    import (
        "bufio"
        "fmt"
        "net"
        "time"
    )
    
    func main() {
        // Устанавливаем соединение с таймаутом
        conn, err := net.DialTimeout("tcp", "localhost:8080", 5*time.Second)
        if err != nil {
            panic(err)
        }
        defer conn.Close()
    
        fmt.Println("Connected to server!")
    
        // Отправляем данные
        _, err = conn.Write([]byte("Hello, server!n"))
        if err != nil {
            panic(err)
        }
    
        // Читаем ответ от сервера
        message, _ := bufio.NewReader(conn).ReadString('n')
        fmt.Print("Message from server: " + message)
    }

В отличие от UDP, который является более быстрым, но ненадёжным протоколом без установки соединения, TCP выбирают тогда, когда потеря или нарушение порядка данных недопустимы.

Ответ 18+ 🔞

А, ну вот, TCP, блядь! Это ж тот самый протокол, который как занудная тётя с бухгалтерии — всё проверит, всё пересчитает, и если где-то копейка потерялась, будет орать до посинения, пока её не найдут. В рот меня чих-пых!

Короче, представь: ты хочешь отправить своему корегу на другой конец страны толстенный архив с фотками твоей поездки на шашлыки. И важно, чтобы всё дошло в идеальном порядке: сначала ты наливаешь, потом ты поджигаешь мангал, а потом уже горит каре. А не наоборот, ёпта! Вот TCP — это как ответственный курьер, который не просто кинет пакет в ящик, а лично вручит, получит роспись, и если что-то по дороге помялось — вернётся и привезёт новое, блядь.

Главные приколы этого протокола:

  • Надёжность до одури (Reliability): Этот упырь не успокоится, пока каждый байт не долетит до цели. Пакет потерялся? Он его снова запросит, сука! Как назойливый коллектор, только для данных.
  • Любовь с первого SYN'a (Connection-oriented): Прежде чем начать болтать, он устраивает целую церемонию «тройного рукопожатия»: «Привет, я SYN» — «Привет, я SYN-ACK» — «Ок, ACK». Только после этого можно скидывать нюдсы, то есть данные. Без этого — ни-ни, блядь.
  • Порядок блюдёт (Ordered Delivery): Он гарантирует, что история будет рассказана по порядку: завязка, кульминация, потом уже пьяная драка в кустах. А не как в плохом монтаже — сразу разбитая морда, а потом «давай знакомиться».
  • Контроль потока (Flow Control): Умная штука, которая не даст тебе, раздолбаю с гигабитным каналом, затопить своего друга, который сидит на модеме через спутник у бабушки в деревне. «Тише, — говорит, — едешь, придурок, он ещё предыдущий пакет разбирает!».

Именно поэтому на TCP ездит всё, что не может позволить себе потерь: твои мемы в браузере (HTTP/HTTPS), файлы на FTP, письма (SMTP) и даже когда ты подключаешься к серверу по SSH, чтобы что-то срочно починить, а вместо этого случайно rm -rf / набираешь. Вот это всё он, этот дотошный тип.

Как с этим жить в Go:

В Go, слава богу, всё уже прикручено в пакете net. Не надо, блядь, паять эти SYN-ACK'и руками.

  1. TCP-сервер, который всех обслуживает Сервер, который слушает порт и на каждое новое соединение заводит отдельную горутину — как таджик-шашлычник на потоке.

    package main
    
    import (
        "fmt"
        "net"
    )
    
    func handleConnection(conn net.Conn) {
        defer conn.Close()
        fmt.Printf("Accepted connection from %sn", conn.RemoteAddr())
        // ... логика чтения/записи данных
        conn.Write([]byte("Hello from server!n"))
    }
    
    func main() {
        // Начинаем слушать порт 8080 по протоколу TCP
        ln, err := net.Listen("tcp", ":8080")
        if err != nil {
            panic(err)
        }
        defer ln.Close()
        fmt.Println("Server is listening on port 8080")
    
        for {
            // Ожидаем и принимаем новое соединение
            conn, err := ln.Accept()
            if err != nil {
                fmt.Println("Error accepting connection:", err)
                continue
            }
            // Обрабатываем соединение в новой горутине
            go handleConnection(conn)
        }
    }
  2. TCP-клиент, который стучится к нам Клиент, который подключается, шлёт привет и ждёт ответа, как пьяный друг под окном.

    package main
    
    import (
        "bufio"
        "fmt"
        "net"
        "time"
    )
    
    func main() {
        // Устанавливаем соединение с таймаутом
        conn, err := net.DialTimeout("tcp", "localhost:8080", 5*time.Second)
        if err != nil {
            panic(err)
        }
        defer conn.Close()
    
        fmt.Println("Connected to server!")
    
        // Отправляем данные
        _, err = conn.Write([]byte("Hello, server!n"))
        if err != nil {
            panic(err)
        }
    
        // Читаем ответ от сервера
        message, _ := bufio.NewReader(conn).ReadString('n')
        fmt.Print("Message from server: " + message)
    }

А чем это лучше UDP? — спросишь ты. Да хуй его знает, чем лучше! UDP — это как крикнуть что-то в толпу: может, дойдёт, а может, и нет. Быстро, но ненадёжно. А TCP — это как заказное письмо с уведомлением: долго, муторно, зато ты точно знаешь, что адресат его получил и даже расписался, ёпта. Выбирай, что тебе важнее: скорость или чтобы всё долетело в целости и сохранности.