Files
LINUX/go/main.go
2026-01-18 22:15:16 +03:00

570 lines
16 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}