Go: сетевые приложения

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 02:10

Это (задумывается) небольшой цикл дополнений к 2-му изданию книги: Книга: Linux: Работаем с сетью ... хотя книга в 1-м издании названа (издателем): Сети Linux. Модели и приложения. Теперь уже пусть так остаётся.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 02:22

Итак...
TCP клиент-сервер, эхо-сервер ретранслирующий клиенту то что от него получено (интересен сам трафик, а не игрища с содержимым пересылаемого).
1-вариант эхо-сервера - простой последовательный ретранслятр:

Код: Выделить всё

package main
import "net"
import "fmt"
import "bufio"

func main() {
  ln, _ := net.Listen("tcp", ":51500")   // Устанавливаем прослушивание порта
  for {
    fmt.Println("waiting on port 51500")
    conn, _ := ln.Accept()               // Открываем серверный порт
    for {                                // Запускаем цикл прослушивания
      // Будем прослушивать все сообщения разделенные \n
      message, err := bufio.NewReader(conn).ReadString('\n')
      if err != nil {
        conn.Close()
        break
      }
      conn.Write([]byte(message + "\n")) // Отправить обратно клиенту
    }
  }
}
Компиляция:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ go build echo-serv.go

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ls -l echo-serv
-rwxrwxr-x 1 olej olej 2758733 дек 17 01:18 echo-serv
Компилятор GoLang (не GCC), сборка статическая ... всё делается по пути наименьшего сопротивления.
Вложения
echo-serv.go
(745 байт) 8 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 02:32

Теперь клиент:

Код: Выделить всё

package main
import "net"
import "fmt"
import "bufio"
import "os"
import "io"
import "strconv"

func main() {
    port := "51500"
    if len(os.Args) > 1 {
        _, err := strconv.Atoi(os.Args[1])
        if err != nil {
            fmt.Print("Ошибочный порт")
            return
        } else {
            port = os.Args[1]
        }
    }
    fmt.Println("connect to port " + port)
    conn, err := net.Dial("tcp", "127.0.0.1:" + port) // Подключаемся к сокету
    if err != nil {
        fmt.Println("no server available"); return
    }
    for {
        reader := bufio.NewReader(os.Stdin)          // Чтение данных из stdin
        fmt.Print("> ")
        text, err := reader.ReadString('\n')
        if err != nil {
            if err != io.EOF { fmt.Print(err) }
            fmt.Println()
            return
        }
        if len(text) == 1 { break }
        fmt.Fprintf(conn, text + "\n")                      // Отправляем в socket
        message, _ := bufio.NewReader(conn).ReadString('\n') // Чтение ответа
        fmt.Print("  " + message)
    }
}
Тут большая часть - это обработка ошибок, которые сильно досаждают потом при тестировании.
Компиляция соответственно:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ go build echo-cli.go

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ls -l echo-cli
-rwxrwxr-x 1 olej olej 2764905 дек 17 01:32 echo-cli
Вложения
echo-cli.go
(1.11 КБ) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 02:43

Olej писал(а):
17 дек 2023, 02:22
1-вариант эхо-сервера
Olej писал(а):
17 дек 2023, 02:32
Теперь клиент:
Это всё draft-версии, что-то будет меняться ... 1-я проба пера.
Но всё это уже пригодно для тестирования:
Это сервер:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-serv 
waiting on port 51500
waiting on port 51500
waiting on port 51500
waiting on port 51500
waiting on port 51500
^C
При каждом подключении клиента он пишет сообщение. Больше ему ни о чём болтать не надо! :lol:
А это вот разные подключения клиента:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 51501
connect to port 51500
> qwqrtr
  qwqrtr
> фыпварф
  фыпварф
> ^C

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 51501
connect to port 51500
> dd
  dd
>

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 51501
connect to port 51500
> 123
  123
> 456
  456
>
Разные они в том смысле, что завершаться (выходить из цикла запросов) они могут по-разному:
- 1-й - по ^C
- 2-й - по ^D - EOF
- 3-й - по пустой строке, Enter
P.S. В этом и состоит необходимость обработки ошибок, чтобы сервер при полузакрытии сокета не впадал в панику.
А вот ошибочные (пока) запуски:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 23
connect to port 23
no server available

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 5102
connect to port 5102
no server available
А это при правильном порте, но не запущенном вообще сервере:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 
connect to port 51500
no server available
Похоже, что нигде никто пока не слетает аварийно. :lol:

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 02:53

Olej писал(а):
17 дек 2023, 02:32
Теперь клиент:
Но куда лучше тестировать не специальными клиентами, а стандартными POSIX-совместимыми CLI утилитами.
Вот 1-й из таких тестеров:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ telnet 127.0.0.1 51500
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
12345
12345

строка передачи
строка передачи

^]
telnet> quit
Connection closed.
А вот 2-й:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ nc 127.0.0.1 51500
1234
1234

asdfghj
asdfghj

^C

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 03:10

Olej писал(а):
17 дек 2023, 02:53
стандартными POSIX-совместимыми CLI утилитами
А вот 3-й:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ echo ещё одна строка | nc 127.0.0.1 51500
ещё одна строка

^C
Вот следующий, 4-й:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ socat tcp4:127.0.0.1:51500 STDOUT
asd
asd

2465245
2465245

^C
P.S. Во всех тестах на приёме добавляется вывод пустой строки \n. Нужно привести в соответствие свои клиент и сервер, чтобы они в отношении переводов строки \n вели себя так же как POSIX-совместимые тесты :!:

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 15:11

Olej писал(а):
17 дек 2023, 03:10
P.S. Во всех тестах на приёме добавляется вывод пустой строки \n. Нужно привести в соответствие свои клиент и сервер, чтобы они в отношении переводов строки \n вели себя так же как POSIX-совместимые тесты
Минимальные изменения в клиенте (отслеживание переводов строк) - так чтобы он вёл себя как другие CLI утилиты:

Код: Выделить всё

package main
import "net"
import "fmt"
import "bufio"
import "os"
import "io"
import "strconv"

func main() {
    port := "51500"
    if len(os.Args) > 1 {
        _, err := strconv.Atoi(os.Args[1])
        if err != nil {
            fmt.Print("Ошибочный порт")
            return
        } else {
            port = os.Args[1]
        }
    }
    fmt.Println("connect to port " + port)
    conn, err := net.Dial("tcp", "127.0.0.1:" + port) // Подключаемся к сокету
    if err != nil {
        fmt.Println("no server available"); return
    }
    for {
        reader := bufio.NewReader(os.Stdin)            // Чтение данных из stdin
        fmt.Print("> ")
        text, err := reader.ReadString('\n')
        if err != nil {
            if err != io.EOF { fmt.Print(err) }
            fmt.Println()
            return
        }
        if len(text) == 1 { break }
        fmt.Fprintf(conn, text + "\n")                      // Отправляем в socket
        message, _ := bufio.NewReader(conn).ReadString('\n') // Чтение ответа
        fmt.Println("  " + message)
    }
}

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli
connect to port 51500
> 12
  12

> 345
  345

> ^C

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ nc 127.0.0.1 51500
12
12

345
345

^C

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ socat tcp4:127.0.0.1:51500 STDOUT
12
12

345
345

^C

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ telnet 127.0.0.1 51500
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
12
12

345
345

^]
telnet> quit
Connection closed.
Вложения
echo-cli.go
(1.11 КБ) 10 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 15:20

Olej писал(а):
17 дек 2023, 02:22
1-вариант эхо-сервера - простой последовательный ретранслятр:
Дальше больше - параллельны сервер, обслуживающий одновременно множество клиентов в параллель :!:
По аналогии:

Код: Выделить всё

package main
import "net"
import "fmt"
import "bufio"

func main() {
  ln, _ := net.Listen("tcp", ":51501")   // Устанавливаем прослушивание порта
  for {                                  // Цикл прослушивания
    fmt.Println("waiting on port 51501")
    conn, _ := ln.Accept()               // Открываем серверный порт
    go func(cn net.Conn) {
      defer func() { cn.Close() }()
      for {                              // Запускаем цикл подключений
        // Будем прослушивать все сообщения разделенные \n
        message, err := bufio.NewReader(cn).ReadString('\n')
        if err != nil {                  // Клиент разорвал коннект
          return 
        }
        cn.Write([]byte(message + "\n")) // Отправить обратно клиенту
      }      
    }(conn)
  }
}
1). очередной раз удивляет с какой лёгкостью и изяществом в Go реализуется последовательная ветвь выполнения :!:
- вызов анониманой функции (лямбда-функции) непосредственно в месте её определения:

Код: Выделить всё

go func(...) { ... } (conn)
- все завершающии операции анониманой функции (по return) выполняются в defer функции (тут тоже анонимная неименоваая функция).
2). параллельный ветки сервера вызываются не как fork() (отдельными процессами), и даже не pthread_t (потоками ядра Linux), а лёгкими сопрограммами Go, которые очень эффективно сосуществуют, и которых может быть до десятков тысяч :!:
Вложения
echo-servm.go
(950 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 15:27

Olej писал(а):
17 дек 2023, 15:20
Дальше больше - параллельны сервер, обслуживающий одновременно множество клиентов в параллель
Сервер:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-servm
waiting on port 51501
waiting on port 51501
waiting on port 51501
...
В разных терминалах, в параллель, запускаю несколько клиентов:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 51501
connect to port 51501
> ^C
olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 51501
connect to port 51501
> A_клиент
  A_клиент

> A_1234567
  A_1234567

> A_строка
  A_строка

>

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 51501
connect to port 51501
> B_клиент
  B_клиент

> B_987654
  B_987654

> B_строка
  B_строка

> ^C
В тексте самих ретранслируемых сообщений я пишу, как идентификацию номера клиента, A_... и B_... - чтобы видеть, что трафик обмена разных параллельных клиентов никак не перепутываются, не интерферируют. (В каждом из клиентов я отправляю сообщения поочерёдно, чередуясь.)

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: сетевые приложения

Непрочитанное сообщение Olej » 17 дек 2023, 15:37

Olej писал(а):
17 дек 2023, 15:27
В разных терминалах, в параллель, запускаю несколько клиентов:
Или так:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-cli 51501
connect to port 51501
> A_123
  A_123

>
...

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ nc 127.0.0.1 51501
B_123
B_123

...

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ socat tcp4:127.0.0.1:51501 STDOUT
C_123
C_123

...

Код: Выделить всё

olej@R420:~$ telnet 127.0.0.1 51501
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
D_123
D_123

...
Все разнообразные клиенты работают с одним экземпляром сервера одновременно, параллельно, не закрываясь:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ ./echo-servm
waiting on port 51501
waiting on port 51501
waiting on port 51501
waiting on port 51501
waiting on port 51501
...

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 23 гостя