Repository: Valery223/ServerTestingLab
Pull request #1: Tcp server
Author: login: Valery223
Created: 2025-03-16T21:13:23Z | Merged: 2025-03-19T08:38:11Z

### handleConnection
горутины на обруботку handleConnection теряются при остановке сервера. 

### acceptConnections
func (s *TCPServer) acceptConnections()  не ждет пока завершатся горутины < -Ну я сонный мог бред написать


Diff:
diff --git a/cmd/tcp-server.go b/cmd/tcp-server.go
new file mode 100644
index 0000000..2225711
--- /dev/null
+++ b/cmd/tcp-server.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"log/slog"
+	"os"
+	"time"
+
+	"github.com/Valery223/ServerTestingLab/internal/server/tcp/server"
+)
+
+// for testing you can use netcat:
+// nc localhost 8080
+
+func main() {
+	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
+	serv, _ := server.NewTCPServer("tcp", "localhost:8080", logger)
+	ctx, cancel := context.WithCancel(context.Background())
+	go serv.Run(ctx)
+
+	var inputCommand string
+	for inputCommand != "stop" {
+		fmt.Scanln(&inputCommand)
+	}
+
+	cancel()
+	time.Sleep(time.Second * 5)
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..e47775e
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/Valery223/ServerTestingLab
+
+go 1.24.0
diff --git a/internal/server/tcp/server/server.go b/internal/server/tcp/server/server.go
new file mode 100644
index 0000000..d60eccf
--- /dev/null
+++ b/internal/server/tcp/server/server.go
@@ -0,0 +1,140 @@
+package server
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"log/slog"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	wg       sync.WaitGroup
+	logger   *slog.Logger
+}
+
+func NewTCPServer(network, address string, logger *slog.Logger) (*TCPServer, error) {
+	listener, err := net.Listen(network, address)
+	if err != nil {
+		return nil, fmt.Errorf("failed to listen %s: %w", address, err)
+	}
+	logger.Info("Server announced",
+		"network", network,
+		"address", address,
+	)
+	return &TCPServer{listener: listener, logger: logger}, nil
+}
+
+func (s *TCPServer) Run(ctx context.Context) {
+	go s.acceptConnections(ctx)
+
+	<-ctx.Done()
+	s.stop()
+}
+
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
+func (s *TCPServer) stop() {
+	s.logger.Info("Server stopping",
+		"address", s.listener.Addr(),
+	)
+	// Прекращение прослушивания сокета
+	s.listener.Close()
+
+	// Ждем завершения tcp соединений
+	s.wg.Wait()
+
+	s.logger.Info("Server stopped", "address", s.listener.Addr())
+}
+
+func (s *TCPServer) acceptConnections(ctx context.Context) {
+	s.logger.Info("Server started accepting connections",
+		"address", s.listener.Addr(),
+		"network", s.listener.Addr().Network(),
+	)
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		default:
+			conn, err := s.listener.Accept()
+			if err != nil {
+				// Проверяем, является ли ошибка результатом закрытия сервера
+				if errors.Is(err, net.ErrClosed) {
+					s.logger.Info("Server is shutting down", "address", s.listener.Addr())
+					return
+				}
+				// Логируем неожиданные ошибки
+				s.logger.Error("Failed to accept connection", "error", err)
+				continue
+			}
+
+			go s.handleConnection(ctx, conn)
+		}
+	}
+}
+
+func (s *TCPServer) handleConnection(ctx context.Context, conn net.Conn) {
+	connLogger := s.logger.With(
+		"remote_addr", conn.RemoteAddr(),
+		"local_addr", conn.LocalAddr(),
+		"network", conn.RemoteAddr().Network(),
+	)
+
+	s.wg.Add(1)
+	defer func() {
+		s.wg.Done()
+		connLogger.Info("Connection closed")
+		if err := conn.Close(); err != nil {
+			connLogger.Error("Error closing connection", "error", err)
+		}
+	}()
+
+	connLogger.Info("New connection established")
+
+	buf := make([]byte, 1024)
+	for {
+		select {
+		case <-ctx.Done():
+			connLogger.Info("Connection closing to server shutdown")
+			return
+		default:
+			// чтобы I/O операция не держала цикл, ведь может прийти сигнал о закрытии - устанавливаем dedline
+			conn.SetReadDeadline(time.Now().Add(time.Second * 5))
+			n, err := conn.Read(buf)
+			if err != nil {
+				if errors.Is(err, os.ErrDeadlineExceeded) {
+					connLogger.Debug("Read timeout")
+					continue
+				} else if errors.Is(err, io.EOF) {
+					connLogger.Debug("Client closed connection: EOF")
+					return
+				} else {
+					connLogger.Error("Read error",
+						"error", err,
+						"is_eof", errors.Is(err, io.EOF),
+					)
+					return
+				}
+			}
+
+			connLogger.Info("Data received",
+				"bytes_read", n,
+				"data", string(buf[:n]),
+			)
+
+			if _, err := conn.Write(buf[:n]); err != nil {
+				connLogger.Error("Write error",
+					"error", err,
+					"bytes_to_write", n,
+				)
+				return
+			}
+		}
+	}
+}


Reviews:
user: login: VelvetApparatus
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T06:18:38Z
user: login: VelvetApparatus
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T17:26:25Z
user: login: VelvetApparatus
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T09:52:32Z
user: login: VelvetApparatus
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T09:53:01Z
user: login: VelvetApparatus
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T09:53:18Z
user: login: VelvetApparatus
body: слушай, прям все лучше и лучше, есть недочеты, но мы для этого мры и сделали
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T09:58:26Z
user: login: Valery223
body: тест1
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-18T10:04:02Z

Review comments:
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
+	}
+	log.Printf("Server unnonced %s", addres)
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: посмотри лучше еще какие-то логгеры. Например `slog` из стандартной либы или `zerolog`
created_at: 2025-03-17T06:07:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
+	}
+	log.Printf("Server unnonced %s", addres)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("Server stopped")
+}
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: отлично, ты контроллишь таким образом поток обработки. Однако посмотри в сторону более оптимальных решений. 
Стоит посмотреть про пакет `context.Context` и структуру контроля потока `select`
created_at: 2025-03-17T06:09:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: хорошее решение исползовать пустую структуру, чем обосновано решение ? 
created_at: 2025-03-17T06:13:14Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: обогащаем ошибку. Хорошая привычка -- делать это сразу 
created_at: 2025-03-17T06:13:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
path: internal/server/tcp/server/server.go
user: login: Valery223
body: этот канал используется только для сигнала об окончании
created_at: 2025-03-17T07:28:46Z
author_association: OWNER
diff_hunk: @@ -25,43 +29,83 @@ func (s *TCPServer) Run() {
 
 	<-s.quit
 
-	log.Println("Server stopped")
+	log.Println("The server began to stop")
 }
 
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
 func (s *TCPServer) Stop() {
+
+	// Информирование tcp соединений о завершении
path: internal/server/tcp/server/server.go
user: login: Valery223
body: "// Информирование tcp соединений о завершении" и **listen сокета**
created_at: 2025-03-17T13:51:07Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,109 @@
+package server
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+	wg       sync.WaitGroup
+}
+
+func NewTCPServer(network, address string) (*TCPServer, error) {
+	listener, err := net.Listen(network, address)
+	if err != nil {
+		return nil, fmt.Errorf("failed to listen %s: %w", address, err)
+	}
+	log.Printf("Server announced %s", address)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("The server began to stop")
+}
+
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
+func (s *TCPServer) Stop() {
+
+	// Информирование tcp соединений о завершении
+	close(s.quit)
+
+	// Прекращение прослушивания сокета
+	s.listener.Close()
+
+	// Ждем завершения tcp соединений
+	s.wg.Wait()
+
+	log.Println("Server is stopped")
+
+}
+
+// План такой: выходим, а handle горутины когда нибудь сами завершаться
+// Проблема: Нет точного времени, гарантирующего завершени их, мы сами не разрываем соединение
+
+func (s *TCPServer) acceptConnections() {
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			conn, err := s.listener.Accept()
+			if err != nil {
+				log.Printf("Failed to accept connection: %s", err)
+				continue
+			}
+			s.wg.Add(1)
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: лучше вызывать добавление в waitGroup там же где и вызывается wg.Done() 
created_at: 2025-03-17T17:22:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+package server
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+	wg       sync.WaitGroup
+}
+
+func NewTCPServer(network, address string) (*TCPServer, error) {
+	listener, err := net.Listen(network, address)
+	if err != nil {
+		return nil, fmt.Errorf("failed to listen %s: %w", address, err)
+	}
+	log.Printf("Server announced %s", address)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("The server began to stop")
+}
+
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
+func (s *TCPServer) Stop() {
+
+	// Информирование tcp соединений о завершении
+	close(s.quit)
+
+	// Прекращение прослушивания сокета
+	s.listener.Close()
+
+	// Ждем завершения tcp соединений
+	s.wg.Wait()
+
+	log.Println("Server is stopped")
+
+}
+
+// План такой: выходим, а handle горутины когда нибудь сами завершаться
+// Проблема: Нет точного времени, гарантирующего завершени их, мы сами не разрываем соединение
+
+func (s *TCPServer) acceptConnections() {
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			conn, err := s.listener.Accept()
+			if err != nil {
+				log.Printf("Failed to accept connection: %s", err)
+				continue
+			}
+			s.wg.Add(1)
+			go s.handleConnection(conn)
+		}
+	}
+}
+
+func (s *TCPServer) handleConnection(conn net.Conn) {
+	// Закрытие соединения
+
+	defer func() {
+		s.wg.Done()
+		log.Println("Connection closed ", conn.RemoteAddr())
+		conn.Close()
+	}()
+
+	log.Printf("New connection: %s", conn.RemoteAddr())
+	buf := make([]byte, 1024)
+	for {
+		select {
+		case <-s.quit:
+			return
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: так у тебя же просто канал закроется, ничего не будет прочитано 
created_at: 2025-03-17T17:25:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+package server
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+	wg       sync.WaitGroup
+}
+
+func NewTCPServer(network, address string) (*TCPServer, error) {
+	listener, err := net.Listen(network, address)
+	if err != nil {
+		return nil, fmt.Errorf("failed to listen %s: %w", address, err)
+	}
+	log.Printf("Server announced %s", address)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("The server began to stop")
+}
+
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
+func (s *TCPServer) Stop() {
+
+	// Информирование tcp соединений о завершении
+	close(s.quit)
+
+	// Прекращение прослушивания сокета
+	s.listener.Close()
+
+	// Ждем завершения tcp соединений
+	s.wg.Wait()
+
+	log.Println("Server is stopped")
+
+}
+
+// План такой: выходим, а handle горутины когда нибудь сами завершаться
+// Проблема: Нет точного времени, гарантирующего завершени их, мы сами не разрываем соединение
+
+func (s *TCPServer) acceptConnections() {
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			conn, err := s.listener.Accept()
+			if err != nil {
+				log.Printf("Failed to accept connection: %s", err)
+				continue
+			}
+			s.wg.Add(1)
+			go s.handleConnection(conn)
+		}
+	}
+}
+
+func (s *TCPServer) handleConnection(conn net.Conn) {
+	// Закрытие соединения
+
+	defer func() {
+		s.wg.Done()
+		log.Println("Connection closed ", conn.RemoteAddr())
+		conn.Close()
+	}()
+
+	log.Printf("New connection: %s", conn.RemoteAddr())
+	buf := make([]byte, 1024)
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			// чтобы I/O операция не держала цикл, ведь может прийти сигнал о закрытии - устанавливаем dedline
+			conn.SetReadDeadline(time.Now().Add(time.Second * 5))
+			n, err := conn.Read(buf)
+			if err != nil {
+				log.Println("handle: ", err)
+				// Если ошибка timeout, то продолжаем
+				if errors.Is(err, os.ErrDeadlineExceeded) {
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: следует такую ошибку логгировать 
created_at: 2025-03-17T17:25:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+package server
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+	wg       sync.WaitGroup
+}
+
+func NewTCPServer(network, address string) (*TCPServer, error) {
+	listener, err := net.Listen(network, address)
+	if err != nil {
+		return nil, fmt.Errorf("failed to listen %s: %w", address, err)
+	}
+	log.Printf("Server announced %s", address)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("The server began to stop")
+}
+
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
+func (s *TCPServer) Stop() {
+
+	// Информирование tcp соединений о завершении
+	close(s.quit)
+
+	// Прекращение прослушивания сокета
+	s.listener.Close()
+
+	// Ждем завершения tcp соединений
+	s.wg.Wait()
+
+	log.Println("Server is stopped")
+
+}
+
+// План такой: выходим, а handle горутины когда нибудь сами завершаться
+// Проблема: Нет точного времени, гарантирующего завершени их, мы сами не разрываем соединение
+
+func (s *TCPServer) acceptConnections() {
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			conn, err := s.listener.Accept()
+			if err != nil {
+				log.Printf("Failed to accept connection: %s", err)
+				continue
+			}
+			s.wg.Add(1)
+			go s.handleConnection(conn)
+		}
+	}
+}
+
+func (s *TCPServer) handleConnection(conn net.Conn) {
+	// Закрытие соединения
+
+	defer func() {
+		s.wg.Done()
+		log.Println("Connection closed ", conn.RemoteAddr())
+		conn.Close()
+	}()
+
+	log.Printf("New connection: %s", conn.RemoteAddr())
+	buf := make([]byte, 1024)
+	for {
+		select {
+		case <-s.quit:
+			return
path: internal/server/tcp/server/server.go
user: login: Valery223
body: так этот канал сигнализирует, что мы останавливаем сервер и следовательно все tcp соединения нужно закрыть. Канал закроется только при закрытии сервера
created_at: 2025-03-18T04:51:11Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,109 @@
+package server
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+	wg       sync.WaitGroup
+}
+
+func NewTCPServer(network, address string) (*TCPServer, error) {
+	listener, err := net.Listen(network, address)
+	if err != nil {
+		return nil, fmt.Errorf("failed to listen %s: %w", address, err)
+	}
+	log.Printf("Server announced %s", address)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("The server began to stop")
+}
+
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
+func (s *TCPServer) Stop() {
+
+	// Информирование tcp соединений о завершении
+	close(s.quit)
+
+	// Прекращение прослушивания сокета
+	s.listener.Close()
+
+	// Ждем завершения tcp соединений
+	s.wg.Wait()
+
+	log.Println("Server is stopped")
+
+}
+
+// План такой: выходим, а handle горутины когда нибудь сами завершаться
+// Проблема: Нет точного времени, гарантирующего завершени их, мы сами не разрываем соединение
+
+func (s *TCPServer) acceptConnections() {
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			conn, err := s.listener.Accept()
+			if err != nil {
+				log.Printf("Failed to accept connection: %s", err)
+				continue
+			}
+			s.wg.Add(1)
+			go s.handleConnection(conn)
+		}
+	}
+}
+
+func (s *TCPServer) handleConnection(conn net.Conn) {
+	// Закрытие соединения
+
+	defer func() {
+		s.wg.Done()
+		log.Println("Connection closed ", conn.RemoteAddr())
+		conn.Close()
+	}()
+
+	log.Printf("New connection: %s", conn.RemoteAddr())
+	buf := make([]byte, 1024)
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			// чтобы I/O операция не держала цикл, ведь может прийти сигнал о закрытии - устанавливаем dedline
+			conn.SetReadDeadline(time.Now().Add(time.Second * 5))
+			n, err := conn.Read(buf)
+			if err != nil {
+				log.Println("handle: ", err)
+				// Если ошибка timeout, то продолжаем
+				if errors.Is(err, os.ErrDeadlineExceeded) {
path: internal/server/tcp/server/server.go
user: login: Valery223
body: Не понял предложения, у меня выше (94 строка), "некое" логирование, а ты говоришь про еще доп логирование, в 97 строке?
created_at: 2025-03-18T04:54:03Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
+	}
+	log.Printf("Server unnonced %s", addres)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("Server stopped")
+}
path: internal/server/tcp/server/server.go
user: login: Valery223
body: Я хотел сделать с потоками и select для понимания, а потом переделать на contex. Можно закрыть тред, в следующем ПР я переделаю?
created_at: 2025-03-18T04:58:57Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
path: internal/server/tcp/server/server.go
user: login: Valery223
body: Переделал, ты закрыл тред?
created_at: 2025-03-18T05:05:10Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
path: internal/server/tcp/server/server.go
user: login: Valery223
body: @VelvetApparatus  "Resolve conversation" как я понимаю ты должен жать, а не я?
created_at: 2025-03-18T05:23:57Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
+	}
+	log.Printf("Server unnonced %s", addres)
path: internal/server/tcp/server/server.go
user: login: Valery223
body: Выполнил: Заменил на slog.
created_at: 2025-03-18T09:43:22Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,109 @@
+package server
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+	wg       sync.WaitGroup
+}
+
+func NewTCPServer(network, address string) (*TCPServer, error) {
+	listener, err := net.Listen(network, address)
+	if err != nil {
+		return nil, fmt.Errorf("failed to listen %s: %w", address, err)
+	}
+	log.Printf("Server announced %s", address)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("The server began to stop")
+}
+
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
+func (s *TCPServer) Stop() {
+
+	// Информирование tcp соединений о завершении
+	close(s.quit)
+
+	// Прекращение прослушивания сокета
+	s.listener.Close()
+
+	// Ждем завершения tcp соединений
+	s.wg.Wait()
+
+	log.Println("Server is stopped")
+
+}
+
+// План такой: выходим, а handle горутины когда нибудь сами завершаться
+// Проблема: Нет точного времени, гарантирующего завершени их, мы сами не разрываем соединение
+
+func (s *TCPServer) acceptConnections() {
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			conn, err := s.listener.Accept()
+			if err != nil {
+				log.Printf("Failed to accept connection: %s", err)
+				continue
+			}
+			s.wg.Add(1)
path: internal/server/tcp/server/server.go
user: login: Valery223
body: Выполнил
created_at: 2025-03-18T09:43:44Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,109 @@
+package server
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+	"time"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+	wg       sync.WaitGroup
+}
+
+func NewTCPServer(network, address string) (*TCPServer, error) {
+	listener, err := net.Listen(network, address)
+	if err != nil {
+		return nil, fmt.Errorf("failed to listen %s: %w", address, err)
+	}
+	log.Printf("Server announced %s", address)
+	return &TCPServer{listener: listener, quit: make(chan struct{})}, nil
+}
+
+func (s *TCPServer) Run() {
+	log.Printf("Server started on %s", s.listener.Addr())
+	go s.acceptConnections()
+
+	<-s.quit
+
+	log.Println("The server began to stop")
+}
+
+// Остановка сервера, это прекращение прослушивания сокета,
+// Завершение всех активных соединений
+func (s *TCPServer) Stop() {
+
+	// Информирование tcp соединений о завершении
+	close(s.quit)
+
+	// Прекращение прослушивания сокета
+	s.listener.Close()
+
+	// Ждем завершения tcp соединений
+	s.wg.Wait()
+
+	log.Println("Server is stopped")
+
+}
+
+// План такой: выходим, а handle горутины когда нибудь сами завершаться
+// Проблема: Нет точного времени, гарантирующего завершени их, мы сами не разрываем соединение
+
+func (s *TCPServer) acceptConnections() {
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			conn, err := s.listener.Accept()
+			if err != nil {
+				log.Printf("Failed to accept connection: %s", err)
+				continue
+			}
+			s.wg.Add(1)
+			go s.handleConnection(conn)
+		}
+	}
+}
+
+func (s *TCPServer) handleConnection(conn net.Conn) {
+	// Закрытие соединения
+
+	defer func() {
+		s.wg.Done()
+		log.Println("Connection closed ", conn.RemoteAddr())
+		conn.Close()
+	}()
+
+	log.Printf("New connection: %s", conn.RemoteAddr())
+	buf := make([]byte, 1024)
+	for {
+		select {
+		case <-s.quit:
+			return
+		default:
+			// чтобы I/O операция не держала цикл, ведь может прийти сигнал о закрытии - устанавливаем dedline
+			conn.SetReadDeadline(time.Now().Add(time.Second * 5))
+			n, err := conn.Read(buf)
+			if err != nil {
+				log.Println("handle: ", err)
+				// Если ошибка timeout, то продолжаем
+				if errors.Is(err, os.ErrDeadlineExceeded) {
path: internal/server/tcp/server/server.go
user: login: Valery223
body: Исправил, теперь логируется как Debug уровень, а если ошибка не времени - Error уровень
created_at: 2025-03-18T09:44:44Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
+	}
+	log.Printf("Server unnonced %s", addres)
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: @Valery223  не смотрел еще ? 
created_at: 2025-03-18T09:52:32Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: @Valery223 отвечаем 
created_at: 2025-03-18T09:53:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
path: internal/server/tcp/server/server.go
user: login: VelvetApparatus
body: @Valery223 поправил не ? 
created_at: 2025-03-18T09:53:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,25 @@
+package main
+
+import (
+	"fmt"
+	"log/slog"
+	"os"
+
+	"github.com/Valery223/ServerTestingLab/internal/server/tcp/server"
+)
+
+// for testing you can use netcat:
+// nc localhost 8080
+
+func main() {
+	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
path: cmd/tcp-server.go
user: login: VelvetApparatus
body: логгер лучше инициализировать в отдельном пакете. 
+ посмотри про `init()`
created_at: 2025-03-18T09:54:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,25 @@
+package main
+
+import (
+	"fmt"
+	"log/slog"
+	"os"
+
+	"github.com/Valery223/ServerTestingLab/internal/server/tcp/server"
+)
+
+// for testing you can use netcat:
+// nc localhost 8080
+
+func main() {
+	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
+	serv, _ := server.NewTCPServer("tcp", "localhost:8080", logger)
path: cmd/tcp-server.go
user: login: VelvetApparatus
body: логгер это больше инфровая штука, не стоит его передавать как аргумент, он должен вызываться глобальным методом/быть глобальной переменной, иметь фабрику или в DI вшиваться 
created_at: 2025-03-18T09:55:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
path: internal/server/tcp/server/server.go
user: login: Valery223
body: Я же ответил

created_at: 2025-03-18T09:56:52Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,67 @@
+package server
+
+import (
+	"log"
+	"net"
+)
+
+type TCPServer struct {
+	listener net.Listener
+	quit     chan struct{}
+}
+
+func NewTCPServer(network, addres string) (*TCPServer, error) {
+	listener, err := net.Listen(network, addres)
+	if err != nil {
+		return nil, err
path: internal/server/tcp/server/server.go
user: login: Valery223
body: да

created_at: 2025-03-18T09:58:50Z
author_association: OWNER

PR comments:
user: login: Valery223
created_at: 2025-03-16T21:16:12Z
body: @VelvetApparatus, ты разбираешься в tcp в Go? Там какой-то прикол можно сделать на время полученя данных, если не успеет, то разрывается соединение(timeout что ли). Короче подобное используется? Или в библиотеках уже все готовое? 
author_association: OWNER
user: login: Valery223
created_at: 2025-03-16T21:18:01Z
body: @VelvetApparatus, так же вопрос про логи(log), правильно ли я вызываю в методах? Или это не используется в проектах?
author_association: OWNER
user: login: VelvetApparatus
created_at: 2025-03-17T06:20:13Z
body: > @VelvetApparatus, ты разбираешься в tcp в Go? Там какой-то прикол можно сделать на время полученя данных, если не успеет, то разрывается соединение(timeout что ли). Короче подобное используется? Или в библиотеках уже все готовое?

Так, а ты смотрел вообще сам пакет `net`? У тебя метод `Listen` - метод структуры `ListenConfig`, просто инициализируй ее явно и пользуйся 
author_association: COLLABORATOR
user: login: VelvetApparatus
created_at: 2025-03-17T06:20:53Z
body: > @VelvetApparatus, так же вопрос про логи(log), правильно ли я вызываю в методах? Или это не используется в проектах?

see https://github.com/Valery223/ServerTestingLab/pull/1#discussion_r1997991386
author_association: COLLABORATOR