Обновить go/main.go
This commit is contained in:
570
go/main.go
570
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)
|
||||
}
|
||||
Reference in New Issue
Block a user