570 lines
16 KiB
Go
570 lines
16 KiB
Go
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)
|
||
} |