From 72fa7b4e27ef4ab6ba5b163e58ae1c9179a642e9 Mon Sep 17 00:00:00 2001 From: ascet Date: Sun, 18 Jan 2026 22:14:54 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20go/main.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/main.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 go/main.go diff --git a/go/main.go b/go/main.go new file mode 100644 index 0000000..e69de29 From f1d220432476ab5bbffea8ca3ede74419e917356 Mon Sep 17 00:00:00 2001 From: ascet Date: Sun, 18 Jan 2026 22:15:16 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20go/main.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/main.go | 570 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 570 insertions(+) diff --git a/go/main.go b/go/main.go index e69de29..eb50873 100644 --- a/go/main.go +++ b/go/main.go @@ -0,0 +1,570 @@ +package main + +import ( + "bufio" + "context" + "flag" + "fmt" + "io" + "log" + "net" + "os" + "os/signal" + "strings" + "sync" + "syscall" + "time" + "unicode" +) + +// ServerState - состояния сервера +type ServerState int + +const ( + StateStopped ServerState = iota + StateRunning + StatePaused +) + +// Config содержит конфигурацию сервера +type Config struct { + Host string + Port int +} + +// Server представляет TCP-сервер +type Server struct { + config Config + listener net.Listener + clients map[net.Conn]bool + clientsMux sync.RWMutex + wg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc + state ServerState + stateMux sync.RWMutex + pauseCh chan struct{} // Канал для уведомления о паузе/продолжении +} + +// NewServer создает новый экземпляр сервера +func NewServer(cfg Config) *Server { + ctx, cancel := context.WithCancel(context.Background()) + return &Server{ + config: cfg, + clients: make(map[net.Conn]bool), + ctx: ctx, + cancel: cancel, + state: StateStopped, + pauseCh: make(chan struct{}, 1), + } +} + +// Start запускает сервер +func (s *Server) Start() error { + s.stateMux.Lock() + if s.state == StateRunning { + s.stateMux.Unlock() + return fmt.Errorf("сервер уже запущен") + } + s.state = StateRunning + s.stateMux.Unlock() + + addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port) + + listener, err := net.Listen("tcp", addr) + if err != nil { + s.stateMux.Lock() + s.state = StateStopped + s.stateMux.Unlock() + return fmt.Errorf("ошибка запуска сервера: %w", err) + } + + s.listener = listener + + // Восстанавливаем контекст если он был отменен + if s.ctx.Err() != nil { + s.ctx, s.cancel = context.WithCancel(context.Background()) + } + + log.Printf("Сервер запущен на %s:%d", s.config.Host, s.config.Port) + log.Print("Ожидание подключений...") + + // Запускаем обработку входящих подключений + s.wg.Add(1) + go s.acceptConnections() + + return nil +} + +// Stop останавливает сервер +func (s *Server) Stop() { + s.stateMux.Lock() + if s.state == StateStopped { + s.stateMux.Unlock() + return + } + + log.Print("Остановка сервера...") + s.state = StateStopped + s.stateMux.Unlock() + + // Отменяем контекст для завершения всех операций + s.cancel() + + // Останавливаем прием новых подключений + if s.listener != nil { + s.listener.Close() + } + + // Закрываем все активные подключения + s.clientsMux.Lock() + for client := range s.clients { + client.Close() + delete(s.clients, client) + } + s.clientsMux.Unlock() + + // Ждем завершения всех горутин с таймаутом + done := make(chan struct{}) + go func() { + s.wg.Wait() + close(done) + }() + + select { + case <-done: + log.Print("Сервер корректно остановлен") + case <-time.After(5 * time.Second): + log.Print("Предупреждение: таймаут при ожидании остановки сервера") + } +} + +// Pause приостанавливает прием новых подключений +func (s *Server) Pause() { + s.stateMux.Lock() + if s.state != StateRunning { + s.stateMux.Unlock() + log.Print("Сервер не запущен или уже на паузе") + return + } + s.state = StatePaused + s.stateMux.Unlock() + + log.Print("Прием новых подключений приостановлен") + + // Останавливаем listener + if s.listener != nil { + s.listener.Close() + } + + // Оповещаем acceptConnections о паузе + select { + case s.pauseCh <- struct{}{}: + default: + } +} + +// Continue возобновляет прием новых подключений +func (s *Server) Continue() { + s.stateMux.Lock() + if s.state != StatePaused { + s.stateMux.Unlock() + log.Print("Сервер не на паузе") + return + } + s.state = StateRunning + s.stateMux.Unlock() + + log.Print("Прием подключений возобновлен") + + // Пересоздаем listener + addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port) + listener, err := net.Listen("tcp", addr) + if err != nil { + log.Printf("Ошибка при возобновлении сервера: %v", err) + s.stateMux.Lock() + s.state = StateStopped + s.stateMux.Unlock() + return + } + + s.listener = listener + + // Оповещаем acceptConnections о продолжении + select { + case s.pauseCh <- struct{}{}: + default: + } +} + +// Restart перезапускает сервер +func (s *Server) Restart() { + log.Print("Перезапуск сервера...") + s.Stop() + time.Sleep(100 * time.Millisecond) // Небольшая задержка для корректной остановки + + // Восстанавливаем контекст + s.ctx, s.cancel = context.WithCancel(context.Background()) + + if err := s.Start(); err != nil { + log.Printf("Ошибка при перезапуске сервера: %v", err) + } else { + log.Print("Сервер перезапущен") + } +} + +// GetState возвращает текущее состояние сервера +func (s *Server) GetState() ServerState { + s.stateMux.RLock() + defer s.stateMux.RUnlock() + return s.state +} + +// acceptConnections принимает входящие подключения +func (s *Server) acceptConnections() { + defer s.wg.Done() + + for { + // Проверяем состояние сервера + s.stateMux.RLock() + state := s.state + s.stateMux.RUnlock() + + if state != StateRunning { + // Ждем изменения состояния + select { + case <-s.pauseCh: + continue + case <-s.ctx.Done(): + return + } + } + + // Устанавливаем таймаут для Accept, чтобы можно было реагировать на отмену + s.listener.(*net.TCPListener).SetDeadline(time.Now().Add(time.Second)) + + conn, err := s.listener.Accept() + if err != nil { + // Проверяем, была ли ошибка из-за таймаута (ожидаемая ситуация) + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + continue + } + + // Проверяем, не был ли листенер закрыт (при паузе или остановке) + select { + case <-s.ctx.Done(): + return + default: + // Если это не таймаут и не отмена, логируем ошибку + log.Printf("Ошибка при принятии подключения: %v", err) + } + continue + } + + // Регистрируем клиента и запускаем обработчик + s.clientsMux.Lock() + s.clients[conn] = true + s.clientsMux.Unlock() + + s.wg.Add(1) + go s.handleConnection(conn) + } +} + +// cleanString очищает строку от невидимых и управляющих символов +func cleanString(s string) string { + // Удаляем пробелы и символы новой строки с обоих концов + s = strings.TrimSpace(s) + + // Удаляем невидимые символы с помощью Map + cleaned := strings.Map(func(r rune) rune { + // Проверяем, является ли символ управляющим + if unicode.IsControl(r) { + // Разрешаем только некоторые управляющие символы + if r == '\t' || r == '\n' || r == '\r' { + return r + } + // Удаляем другие управляющие символы + return -1 + } + + // Удаляем специфичные невидимые символы + switch r { + case '\uFEFF', // Zero Width No-Break Space (BOM) + '\u200B', // Zero Width Space + '\u200E', // Left-to-Right Mark + '\u200F', // Right-to-Left Mark + '\u202A', // Left-to-Right Embedding + '\u202B', // Right-to-Left Embedding + '\u202C', // Pop Directional Formatting + '\u202D', // Left-to-Right Override + '\u202E': // Right-to-Left Override + return -1 + default: + return r + } + }, s) + + return cleaned +} + +// handleConnection обрабатывает подключение клиента +func (s *Server) handleConnection(conn net.Conn) { + defer func() { + // Удаляем клиента из списка при закрытии соединения + s.clientsMux.Lock() + delete(s.clients, conn) + s.clientsMux.Unlock() + + conn.Close() + s.wg.Done() + + log.Printf("Клиент отключен: %s", conn.RemoteAddr()) + }() + + clientAddr := conn.RemoteAddr().String() + log.Printf("Подключен клиент: %s", clientAddr) + + // Получаем IP и порт клиента + clientIP, clientPort, err := net.SplitHostPort(clientAddr) + if err != nil { + log.Printf("Ошибка парсинга адреса клиента: %v", err) + clientIP = "неизвестно" + clientPort = "неизвестно" + } + + reader := bufio.NewReader(conn) + writer := bufio.NewWriter(conn) + + // Отправляем приветственное сообщение клиенту + welcomeMessage := fmt.Sprintf("hello %s:%s\n", clientIP, clientPort) + _, err = writer.WriteString(welcomeMessage) + if err != nil { + log.Printf("Ошибка отправки приветствия клиенту %s: %v", clientAddr, err) + return + } + writer.Flush() + log.Printf("Отправлено приветствие клиенту: %s", welcomeMessage[:len(welcomeMessage)-1]) + + for { + select { + case <-s.ctx.Done(): + return + default: + // Устанавливаем таймаут на чтение + conn.SetReadDeadline(time.Now().Add(10 * time.Second)) + + // Читаем строку до символа новой строки + message, err := reader.ReadString('\n') + if err != nil { + if err == io.EOF { + log.Printf("Клиент отключился: %s", clientAddr) + } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + // Таймаут - продолжаем цикл + continue + } else { + log.Printf("Ошибка чтения от клиента %s: %v", clientAddr, err) + } + return + } + + // Очищаем строку от невидимых символов + message = cleanString(message) + + if message == "" { + continue + } + + // Отладочный вывод + log.Printf("Получено от %s: '%s' (длина: %d, байты: %v)", + clientAddr, message, len(message), []byte(message)) + + // Переворачиваем строку + reversed := reverseString(message) + log.Printf("Отправка: '%s' (длина: %d, байты: %v)", + reversed, len(reversed), []byte(reversed)) + + // Отправляем перевернутую строку обратно клиенту + _, err = writer.WriteString(reversed + "\n") + if err != nil { + log.Printf("Ошибка отправки клиенту %s: %v", clientAddr, err) + return + } + writer.Flush() + } + } +} + +// reverseString переворачивает строку (безопасная версия) +func reverseString(s string) string { + // Проверяем на пустую строку + if len(s) == 0 { + return s + } + + // Преобразуем в руны для корректной работы с Unicode + runes := []rune(s) + n := len(runes) + + // Переворачиваем массив рун + for i := 0; i < n/2; i++ { + runes[i], runes[n-1-i] = runes[n-1-i], runes[i] + } + + // Преобразуем обратно в строку + return string(runes) +} + +// showStatus показывает текущий статус сервера +func (s *Server) showStatus() { + state := s.GetState() + var status string + switch state { + case StateStopped: + status = "ОСТАНОВЛЕН" + case StateRunning: + status = "РАБОТАЕТ" + case StatePaused: + status = "НА ПАУЗЕ" + } + + s.clientsMux.RLock() + clientCount := len(s.clients) + s.clientsMux.RUnlock() + + fmt.Print("\n=== Статус сервера ===\n") + fmt.Printf("Состояние: %s\n", status) + fmt.Printf("Подключенных клиентов: %d\n", clientCount) + fmt.Printf("Адрес: %s:%d\n", s.config.Host, s.config.Port) + fmt.Print("====================\n") +} + +// CommandHandler обрабатывает команды из консоли +func CommandHandler(server *Server) { + scanner := bufio.NewScanner(os.Stdin) + + // Список доступных команд + fmt.Print("\n=== Управление сервером ===\n") + fmt.Print("Доступные команды:\n") + fmt.Print(" start - запустить сервер\n") + fmt.Print(" stop - остановить сервер\n") + fmt.Print(" pause - приостановить прием новых подключений\n") + fmt.Print(" continue - возобновить прием подключений\n") + fmt.Print(" restart - перезапустить сервер\n") + fmt.Print(" status - показать статус сервера\n") + fmt.Print(" help - показать эту справку\n") + fmt.Print(" exit - завершить работу\n") + fmt.Print("==========================\n") + + fmt.Print("\n> ") + + for scanner.Scan() { + input := strings.TrimSpace(scanner.Text()) + + switch strings.ToLower(input) { + case "start": + if err := server.Start(); err != nil { + fmt.Printf("Ошибка: %v\n", err) + } else { + fmt.Print("Сервер запущен\n") + } + + case "stop": + server.Stop() + fmt.Print("Сервер остановлен\n") + + case "pause": + server.Pause() + fmt.Print("Сервер на паузе\n") + + case "continue": + server.Continue() + fmt.Print("Сервер возобновил работу\n") + + case "restart": + server.Restart() + fmt.Print("Сервер перезапущен\n") + + case "status": + server.showStatus() + + case "help": + fmt.Print("\nДоступные команды:\n") + fmt.Print(" start - запустить сервер\n") + fmt.Print(" stop - остановить сервер\n") + fmt.Print(" pause - приостановить прием новых подключений\n") + fmt.Print(" continue - возобновить прием подключений\n") + fmt.Print(" restart - перезапустить сервер\n") + fmt.Print(" status - показать статус сервера\n") + fmt.Print(" help - показать эту справку\n") + fmt.Print(" exit - завершить работу\n") + + case "exit": + fmt.Print("Завершение работы...\n") + server.Stop() + os.Exit(0) + + case "": + // Пустая строка - ничего не делаем + + default: + fmt.Print("Неизвестная команда. Введите 'help' для списка команд.\n") + } + + fmt.Print("> ") + } + + if err := scanner.Err(); err != nil { + log.Printf("Ошибка чтения команд: %v", err) + } +} + +func main() { + // Настройка логгера + log.SetFlags(log.LstdFlags | log.Lshortfile) + + // Парсинг аргументов командной строки + var ( + host string + port int + ) + + flag.StringVar(&host, "host", "0.0.0.0", "IP адрес для прослушивания") + flag.StringVar(&host, "h", "0.0.0.0", "IP адрес для прослушивания (сокращенно)") + flag.IntVar(&port, "port", 1771, "Порт для прослушивания") + flag.IntVar(&port, "p", 1771, "Порт для прослушивания (сокращенно)") + flag.Parse() + + // Проверка порта + if port < 1 || port > 65535 { + log.Fatal("Порт должен быть в диапазоне 1-65535") + } + + // Создаем сервер + config := Config{ + Host: host, + Port: port, + } + + server := NewServer(config) + + // Обработка сигналов ОС (Ctrl+C) + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + sig := <-sigChan + log.Printf("Получен сигнал: %v", sig) + server.Stop() + os.Exit(0) + }() + + // Автозапуск сервера при старте + if err := server.Start(); err != nil { + log.Printf("Ошибка автозапуска сервера: %v", err) + fmt.Print("Используйте команду 'start' для ручного запуска.\n") + } + + // Запускаем обработчик команд + CommandHandler(server) +} \ No newline at end of file From 74c0b56c41563ac647d882f187d044b4d11433c0 Mon Sep 17 00:00:00 2001 From: ascet Date: Sun, 18 Jan 2026 22:16:04 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20go/go.mod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/go.mod | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 go/go.mod diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..e4be819 --- /dev/null +++ b/go/go.mod @@ -0,0 +1,3 @@ +module f-srv + +go 1.21 \ No newline at end of file