Обновить go/main.go

This commit is contained in:
2026-01-18 22:15:16 +03:00
parent 72fa7b4e27
commit f1d2204324

View File

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