diff --git a/hw11_telnet_client/.sync b/hw11_telnet_client/.sync deleted file mode 100644 index e69de29..0000000 diff --git a/hw11_telnet_client/main.go b/hw11_telnet_client/main.go index 307acaf..939fe94 100644 --- a/hw11_telnet_client/main.go +++ b/hw11_telnet_client/main.go @@ -1,6 +1,96 @@ package main +import ( + "bufio" + "bytes" + "context" + "flag" + "fmt" + "io" + "os" + "os/signal" + "sync" + "syscall" + "time" +) + func main() { - // Place your code here, - // P.S. Do not rush to throw context down, think think if it is useful with blocking operation? + flag.Parse() + port := os.Args[len(os.Args)-1] + host := os.Args[len(os.Args)-2] + duration := os.Args[len(os.Args)-3] + + durVal, err := time.ParseDuration(duration) + if err != nil { + panic("wrong duration value " + duration) + } + + in := &bytes.Buffer{} + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, os.Interrupt) + defer stop() + + client := NewTelnetClient(host+":"+port, durVal, io.NopCloser(in), os.Stdout) + defer client.Close() + + err = client.Connect() + if err != nil { + fmt.Printf("connection failed: %s\n", err) + return + } + + var wg sync.WaitGroup + + wg.Add(2) + go sendByTcp(ctx, stop, &wg, in, client) + go connectionHandler(ctx, &wg, client) + + wg.Wait() +} + +func sendByTcp(ctx context.Context, stop context.CancelFunc, wg *sync.WaitGroup, in *bytes.Buffer, t TelnetClient) { + go func() { + <-ctx.Done() + err := t.Close() + if err != nil { + fmt.Printf("failed to colse connection %s", err) + } + wg.Done() + }() + + reader := bufio.NewReader(os.Stdin) + for { + resp, err := reader.ReadString('\n') + if err != nil { + stop() + return + } + + in.WriteString(resp) + err = t.Send() + if err != nil { + fmt.Println(err) + return + } + } +} + +func connectionHandler(ctx context.Context, wg *sync.WaitGroup, t TelnetClient) { + errCh := make(chan error) + defer func() { + wg.Done() + }() + + for { + select { + case <-ctx.Done(): + return + case errCh <- t.Receive(): + err := <-errCh + if err != nil { + fmt.Println(err) + return + } + } + } } diff --git a/hw11_telnet_client/telnet.go b/hw11_telnet_client/telnet.go index 369caa1..f89c634 100644 --- a/hw11_telnet_client/telnet.go +++ b/hw11_telnet_client/telnet.go @@ -1,7 +1,10 @@ package main import ( + "errors" + "fmt" "io" + "net" "time" ) @@ -12,10 +15,64 @@ type TelnetClient interface { Receive() error } +type ConnectionParams struct { + Address string + Timeout time.Duration + In io.ReadCloser + Out io.Writer + Conn net.Conn +} + func NewTelnetClient(address string, timeout time.Duration, in io.ReadCloser, out io.Writer) TelnetClient { - // Place your code here. + return &ConnectionParams{ + Address: address, + Timeout: timeout, + In: in, + Out: out, + } +} + +func (c *ConnectionParams) Connect() error { + conn, err := net.DialTimeout("tcp", c.Address, c.Timeout) + if err != nil { + return fmt.Errorf("connection error: %w", err) + } + + c.Conn = conn + fmt.Println("connected to ", c.Address) + return nil } -// Place your code here. -// P.S. Author's solution takes no more than 50 lines. +func (c *ConnectionParams) Close() error { + if c.Conn != nil { + if err := c.Conn.Close(); err != nil { + return fmt.Errorf("close error: %w", err) + } + } + + return nil +} + +func (c *ConnectionParams) Send() error { + size, err := io.Copy(c.Conn, c.In) + if err != nil { + return fmt.Errorf("send error: %w", err) + } + fmt.Printf("sent %d bytes", size) + fmt.Println() + + return nil +} + +func (c *ConnectionParams) Receive() error { + _, err := io.Copy(c.Out, c.Conn) + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return fmt.Errorf("receive error: %w", err) + } + + return nil +} diff --git a/hw11_telnet_client/telnet_test.go b/hw11_telnet_client/telnet_test.go index fb89f6d..eee771b 100644 --- a/hw11_telnet_client/telnet_test.go +++ b/hw11_telnet_client/telnet_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/require" //nolint:depguard ) func TestTelnetClient(t *testing.T) {