Initial commit
This commit is contained in:
0
hw11_telnet_client/.sync
Normal file
0
hw11_telnet_client/.sync
Normal file
109
hw11_telnet_client/README.md
Normal file
109
hw11_telnet_client/README.md
Normal file
@@ -0,0 +1,109 @@
|
||||
## Домашнее задание №11 «Клиент TELNET»
|
||||
|
||||
Необходимо реализовать крайне примитивный TELNET клиент
|
||||
(без поддержки команд, опций и протокола в целом).
|
||||
|
||||
Примеры вызовов:
|
||||
```bash
|
||||
$ go-telnet --timeout=10s host port
|
||||
$ go-telnet mysite.ru 8080
|
||||
$ go-telnet --timeout=3s 1.1.1.1 123
|
||||
```
|
||||
|
||||
* Программа должна подключаться к указанному хосту (IP или доменное имя) и порту по протоколу TCP.
|
||||
* После подключения STDIN программы должен записываться в сокет,
|
||||
а данные, полученные из сокета, должны выводиться в STDOUT - всё происходит конкурентно.
|
||||
* Опционально в программу можно передать таймаут на подключение к серверу
|
||||
(через аргумент `--timeout`) - по умолчанию `10s`.
|
||||
* При нажатии `Ctrl+D` программа должна закрывать сокет и завершаться с сообщением.
|
||||
* При получении `SIGINT` программа должна завершать свою работу.
|
||||
* Если сокет закрылся со стороны сервера, то при следующей попытке отправить сообщение программа
|
||||
должна завершаться (допускается завершать программу после "неудачной" отправки нескольких сообщений).
|
||||
* При подключении к несуществующему серверу, программа должна завершаться с ошибкой соединения/таймаута.
|
||||
|
||||
При необходимости можно выделять дополнительные функции / ошибки.
|
||||
|
||||
Примеры работы:
|
||||
|
||||
1) Сервер обрывает соединение
|
||||
```bash
|
||||
$ nc -l localhost 4242
|
||||
Hello from NC
|
||||
I'm telnet client
|
||||
Bye, client!
|
||||
^C
|
||||
```
|
||||
|
||||
```bash
|
||||
$ go-telnet --timeout=5s localhost 4242
|
||||
...Connected to localhost:4242
|
||||
Hello from NC
|
||||
I'm telnet client
|
||||
Bye, client!
|
||||
Bye-bye
|
||||
...Connection was closed by peer
|
||||
```
|
||||
|
||||
Здесь сообщения
|
||||
```
|
||||
Hello from NC
|
||||
Bye, client!
|
||||
```
|
||||
и операция Ctrl+C (Unix) относятся к `nc`,
|
||||
|
||||
а сообщения
|
||||
```
|
||||
I'm telnet client
|
||||
Bye-bye
|
||||
```
|
||||
относятся к `go-telnet`.
|
||||
|
||||
2) Клиент завершает ввод
|
||||
```bash
|
||||
$ go-telnet localhost 4242
|
||||
...Connected to localhost:4242
|
||||
I
|
||||
will be
|
||||
back!
|
||||
^D
|
||||
...EOF
|
||||
```
|
||||
|
||||
```bash
|
||||
$ nc -l localhost 4242
|
||||
I
|
||||
will be
|
||||
back!
|
||||
```
|
||||
|
||||
Здесь сообщения
|
||||
```
|
||||
I
|
||||
will be
|
||||
back!
|
||||
```
|
||||
и операция Ctrl+D (Unix) относятся к `go-telnet`,
|
||||
|
||||
Сообщения
|
||||
```
|
||||
...Connected to localhost:4242
|
||||
...Connection was closed by peer
|
||||
...EOF
|
||||
```
|
||||
являются служебными.
|
||||
Они **выводятся в STDERR** и не тестируются `test.sh`, их формат на усмотрение автора.
|
||||
|
||||
### Критерии оценки
|
||||
- Пайплайн зелёный - 4 балла
|
||||
- Добавлены юнит-тесты - до 2 баллов
|
||||
- Понятность и чистота кода - до 4 баллов
|
||||
|
||||
#### Зачёт от 7 баллов
|
||||
|
||||
### Подсказки
|
||||
- Для ручного тестирования рекомендуется использовать nc (как в `test.sh`).
|
||||
- `flag.DurationVar`
|
||||
- `net.JoinHostPort`, `net.DialTimeout`
|
||||
- `bufio` / `io`
|
||||
- `signal.NotifyContext`
|
||||
- https://stackoverflow.com/questions/51317968/write-on-a-closed-net-conn-but-returned-nil-error
|
||||
11
hw11_telnet_client/go.mod
Normal file
11
hw11_telnet_client/go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module github.com/fixme_my_friend/hw11_telnet_client
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/stretchr/testify v1.7.0
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
13
hw11_telnet_client/go.sum
Normal file
13
hw11_telnet_client/go.sum
Normal file
@@ -0,0 +1,13 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
6
hw11_telnet_client/main.go
Normal file
6
hw11_telnet_client/main.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
// Place your code here,
|
||||
// P.S. Do not rush to throw context down, think think if it is useful with blocking operation?
|
||||
}
|
||||
21
hw11_telnet_client/telnet.go
Normal file
21
hw11_telnet_client/telnet.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TelnetClient interface {
|
||||
Connect() error
|
||||
io.Closer
|
||||
Send() error
|
||||
Receive() error
|
||||
}
|
||||
|
||||
func NewTelnetClient(address string, timeout time.Duration, in io.ReadCloser, out io.Writer) TelnetClient {
|
||||
// Place your code here.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Place your code here.
|
||||
// P.S. Author's solution takes no more than 50 lines.
|
||||
65
hw11_telnet_client/telnet_test.go
Normal file
65
hw11_telnet_client/telnet_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTelnetClient(t *testing.T) {
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:")
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, l.Close()) }()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
in := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
|
||||
timeout, err := time.ParseDuration("10s")
|
||||
require.NoError(t, err)
|
||||
|
||||
client := NewTelnetClient(l.Addr().String(), timeout, io.NopCloser(in), out)
|
||||
require.NoError(t, client.Connect())
|
||||
defer func() { require.NoError(t, client.Close()) }()
|
||||
|
||||
in.WriteString("hello\n")
|
||||
err = client.Send()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Receive()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "world\n", out.String())
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
conn, err := l.Accept()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, conn)
|
||||
defer func() { require.NoError(t, conn.Close()) }()
|
||||
|
||||
request := make([]byte, 1024)
|
||||
n, err := conn.Read(request)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "hello\n", string(request)[:n])
|
||||
|
||||
n, err = conn.Write([]byte("world\n"))
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, n)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
34
hw11_telnet_client/test.sh
Executable file
34
hw11_telnet_client/test.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -xeuo pipefail
|
||||
|
||||
go build -o go-telnet
|
||||
|
||||
(echo -e "Hello\nFrom\nNC\n" && cat 2>/dev/null) | nc -l localhost 4242 >/tmp/nc.out &
|
||||
NC_PID=$!
|
||||
|
||||
sleep 1
|
||||
(echo -e "I\nam\nTELNET client\n" && cat 2>/dev/null) | ./go-telnet --timeout=5s localhost 4242 >/tmp/telnet.out &
|
||||
TL_PID=$!
|
||||
|
||||
sleep 5
|
||||
kill ${TL_PID} 2>/dev/null || true
|
||||
kill ${NC_PID} 2>/dev/null || true
|
||||
|
||||
function fileEquals() {
|
||||
local fileData
|
||||
fileData=$(cat "$1")
|
||||
[ "${fileData}" = "${2}" ] || (echo -e "unexpected output, $1:\n${fileData}" && exit 1)
|
||||
}
|
||||
|
||||
expected_nc_out='I
|
||||
am
|
||||
TELNET client'
|
||||
fileEquals /tmp/nc.out "${expected_nc_out}"
|
||||
|
||||
expected_telnet_out='Hello
|
||||
From
|
||||
NC'
|
||||
fileEquals /tmp/telnet.out "${expected_telnet_out}"
|
||||
|
||||
rm -f go-telnet
|
||||
echo "PASS"
|
||||
Reference in New Issue
Block a user