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) }