Go: сетевые приложения
Модератор: Olej
- Olej
- Писатель
- Сообщения: 21338
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Go: сетевые приложения
- Olej
- Писатель
- Сообщения: 21338
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Go: сетевые приложения
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
- Вложения
-
- echo-serv.go
- (745 байт) 8 скачиваний
- Olej
- Писатель
- Сообщения: 21338
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Go: сетевые приложения
Код: Выделить всё
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: сетевые приложения
Это всё 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
А это вот разные подключения клиента:
Код: Выделить всё
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
- Olej
- Писатель
- Сообщения: 21338
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Go: сетевые приложения
Но куда лучше тестировать не специальными клиентами, а стандартными 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.
Код: Выделить всё
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: сетевые приложения
А вот 3-й:
Код: Выделить всё
olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ echo ещё одна строка | nc 127.0.0.1 51500
ещё одна строка
^C
Код: Выделить всё
olej@R420:~/2023/own.BOOKs/NET.2/Nexamples.WORK/Go$ socat tcp4:127.0.0.1:51500 STDOUT
asd
asd
2465245
2465245
^C
- Olej
- Писатель
- Сообщения: 21338
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Go: сетевые приложения
Минимальные изменения в клиенте (отслеживание переводов строк) - так чтобы он вёл себя как другие 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: сетевые приложения
Дальше больше - параллельны сервер, обслуживающий одновременно множество клиентов в параллель
По аналогии:
Код: Выделить всё
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)
}
}
- вызов анониманой функции (лямбда-функции) непосредственно в месте её определения:
Код: Выделить всё
go func(...) { ... } (conn)
2). параллельный ветки сервера вызываются не как fork() (отдельными процессами), и даже не pthread_t (потоками ядра Linux), а лёгкими сопрограммами Go, которые очень эффективно сосуществуют, и которых может быть до десятков тысяч
- Вложения
-
- echo-servm.go
- (950 байт) 9 скачиваний
- Olej
- Писатель
- Сообщения: 21338
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Go: сетевые приложения
Сервер:
Код: Выделить всё
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
- Olej
- Писатель
- Сообщения: 21338
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Go: сетевые приложения
Или так:
Код: Выделить всё
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
...
Кто сейчас на конференции
Сейчас этот форум просматривают: нет зарегистрированных пользователей и 17 гостей