package main import ( "bufio" "context" "encoding/json" "fmt" "io" "log" "net" "os" "os/signal" "path/filepath" "sort" "strings" "sync" "syscall" "time" ) // ==================== 配置 ==================== type Config struct { Server ServerConfig `json:"server"` Remote RemoteConfig `json:"remote"` Timing TimingConfig `json:"timing"` Log LogConfig `json:"log"` Reverse bool `json:"reverse"` // true=正(翻转发送逻辑), false=反(默认) } type ServerConfig struct { Port int `json:"port"` // 本机 TCP 监听端口 } type RemoteConfig struct { Host string `json:"host"` // 远端 TCP 服务地址 Port int `json:"port"` // 远端 TCP 服务端口 DialTimeout int `json:"dial_timeout_seconds"` // 连接超时 } type TimingConfig struct { StartupDelaySeconds int `json:"startup_delay_seconds"` // 启动后延迟发送 ResendDelaySeconds int `json:"resend_delay_seconds"` // 收到后重发延迟 } type LogConfig struct { Dir string `json:"dir"` // 日志目录 Level string `json:"level"` // debug/info/warn/error KeepDays int `json:"keep_days"` // 日志保留天数 } func defaultConfig() Config { return Config{ Server: ServerConfig{Port: 9600}, Remote: RemoteConfig{ Host: "127.0.0.1", Port: 9000, DialTimeout: 5, }, Timing: TimingConfig{ StartupDelaySeconds: 180, ResendDelaySeconds: 180, }, Log: LogConfig{ Dir: "logs", Level: "info", KeepDays: 60, }, Reverse: false, } } func loadConfig(path string) (*Config, error) { cfg := defaultConfig() data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { log.Printf("[WARN] 配置文件 %s 不存在,使用默认配置", path) return &cfg, nil } return nil, fmt.Errorf("读取配置文件失败: %w", err) } if err := json.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("解析配置文件失败: %w", err) } if cfg.Server.Port <= 0 || cfg.Server.Port > 65535 { return nil, fmt.Errorf("无效的服务端口: %d", cfg.Server.Port) } if cfg.Remote.Port <= 0 || cfg.Remote.Port > 65535 { return nil, fmt.Errorf("无效的远端端口: %d", cfg.Remote.Port) } if cfg.Log.KeepDays <= 0 { cfg.Log.KeepDays = 60 } if cfg.Log.Dir == "" { cfg.Log.Dir = "logs" } return &cfg, nil } // ==================== 按天滚动日志 ==================== type DailyLogger struct { dir string level string keepDays int logger *log.Logger mu sync.Mutex date string // 当前日志日期 file *os.File } func NewDailyLogger(dir, level string, keepDays int) *DailyLogger { os.MkdirAll(dir, 0755) dl := &DailyLogger{ dir: dir, level: level, keepDays: keepDays, } dl.rotate() return dl } func (dl *DailyLogger) rotate() { today := time.Now().Format("2006-01-02") if dl.date == today && dl.logger != nil { return } // 关闭旧文件 if dl.file != nil { dl.file.Close() } dl.date = today filename := filepath.Join(dl.dir, fmt.Sprintf("tcp_service_%s.log", today)) f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { log.Printf("[ERROR] 无法创建日志文件 %s: %v", filename, err) dl.logger = log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds) dl.file = nil return } dl.file = f multiWriter := io.MultiWriter(os.Stdout, f) dl.logger = log.New(multiWriter, "", log.LstdFlags|log.Lmicroseconds) // 清理过期日志 go dl.cleanOldLogs() } func (dl *DailyLogger) cleanOldLogs() { entries, err := os.ReadDir(dl.dir) if err != nil { return } cutoff := time.Now().AddDate(0, 0, -dl.keepDays) var expired []string for _, e := range entries { if e.IsDir() || !strings.HasPrefix(e.Name(), "tcp_service_") { continue } // 从文件名提取日期: tcp_service_2006-01-02.log name := e.Name() dateStr := strings.TrimPrefix(name, "tcp_service_") dateStr = strings.TrimSuffix(dateStr, ".log") t, err := time.Parse("2006-01-02", dateStr) if err != nil { continue } if t.Before(cutoff) { expired = append(expired, filepath.Join(dl.dir, name)) } } sort.Strings(expired) for _, p := range expired { os.Remove(p) dl.Logf("[INFO] 清理过期日志: %s", filepath.Base(p)) } } // Logf 写日志(自动检查日期滚动) func (dl *DailyLogger) Logf(format string, args ...interface{}) { dl.mu.Lock() defer dl.mu.Unlock() today := time.Now().Format("2006-01-02") if dl.date != today { dl.rotate() } msg := fmt.Sprintf(format, args...) dl.logger.Println(msg) } // ==================== 远端 TCP 客户端 ==================== type TCPClient struct { host string port int timeout time.Duration log *DailyLogger mu sync.Mutex conn net.Conn writer *bufio.Writer ctx context.Context cancel context.CancelFunc } func NewTCPClient(host string, port int, dialTimeoutSec int, log *DailyLogger) *TCPClient { ctx, cancel := context.WithCancel(context.Background()) return &TCPClient{ host: host, port: port, timeout: time.Duration(dialTimeoutSec) * time.Second, log: log, ctx: ctx, cancel: cancel, } } func (c *TCPClient) Connect() error { c.mu.Lock() defer c.mu.Unlock() if c.conn != nil { c.conn.Close() } addr := fmt.Sprintf("%s:%d", c.host, c.port) c.log.Logf("[INFO] 正在连接远端 %s ...", addr) d := net.Dialer{Timeout: c.timeout} conn, err := d.DialContext(c.ctx, "tcp", addr) if err != nil { return fmt.Errorf("连接远端 %s 失败: %w", addr, err) } conn.SetDeadline(time.Time{}) c.conn = conn c.writer = bufio.NewWriter(conn) c.log.Logf("[INFO] 已连接远端 %s", addr) go c.receiveLoop() return nil } func (c *TCPClient) Send(msg string) error { // 最多重试 2 次(首次 + 1次重连重试) for attempt := 0; attempt < 2; attempt++ { // 确保连接存在 c.mu.Lock() needConnect := c.conn == nil c.mu.Unlock() if needConnect { if err := c.Connect(); err != nil { return err } } c.mu.Lock() if c.conn == nil { c.mu.Unlock() continue } c.log.Logf("[INFO] >>> 发送远端: %s", msg) _, err := c.writer.WriteString(msg) if err != nil { c.log.Logf("[ERROR] 发送失败: %v, 重试中...", err) c.closeLocked() c.mu.Unlock() continue } if err := c.writer.Flush(); err != nil { c.log.Logf("[ERROR] Flush失败: %v, 重试中...", err) c.closeLocked() c.mu.Unlock() continue } c.mu.Unlock() return nil } return fmt.Errorf("发送失败: 重试次数已用完") } func (c *TCPClient) receiveLoop() { c.mu.Lock() conn := c.conn c.mu.Unlock() if conn == nil { return } reader := bufio.NewReader(conn) for { select { case <-c.ctx.Done(): return default: } line, err := reader.ReadString('\n') if err != nil { if err == io.EOF { c.log.Logf("[WARN] 远端关闭了连接") } else { c.log.Logf("[WARN] 远端接收错误: %v", err) } // 只关闭自己的连接,避免误关已被替换的新连接 c.mu.Lock() if c.conn == conn { c.closeLocked() } else { conn.Close() } c.mu.Unlock() return } c.log.Logf("[INFO] <<< 远端返回: %s", strings.TrimRight(line, "\r\n")) } } func (c *TCPClient) closeLocked() { if c.conn != nil { c.conn.Close() c.conn = nil c.writer = nil } } func (c *TCPClient) Close() { c.cancel() c.mu.Lock() defer c.mu.Unlock() c.closeLocked() } // ==================== TCP 服务端 ==================== type TCPServer struct { port int client *TCPClient cfg *TimingConfig log *DailyLogger reverse bool ctx context.Context cancel context.CancelFunc autoOffTimer *time.Timer // 自动关灯定时器 timerMu sync.Mutex } func NewTCPServer(port int, client *TCPClient, cfg *TimingConfig, reverse bool, log *DailyLogger) *TCPServer { ctx, cancel := context.WithCancel(context.Background()) return &TCPServer{ port: port, client: client, cfg: cfg, log: log, reverse: reverse, ctx: ctx, cancel: cancel, } } // sendMsg 发送消息(根据 reverse 参数翻转) func (s *TCPServer) sendMsg(msg string) error { if s.reverse { msg = flipMsg(msg) } return s.client.Send(msg) } // flipMsg 翻转消息内容 <1,0,post> ↔ <1,1,post> func flipMsg(msg string) string { switch msg { case "<1,0,post>": return "<1,1,post>" case "<1,1,post>": return "<1,0,post>" default: return msg } } // flipMsgCond 根据条件翻转(用于 main 等非 TCPServer 上下文) func flipMsgCond(msg string, reverse bool) string { if reverse { return flipMsg(msg) } return msg } func (s *TCPServer) Start() error { addr := fmt.Sprintf(":%d", s.port) listener, err := net.Listen("tcp", addr) if err != nil { return fmt.Errorf("TCP 监听 %s 失败: %w", addr, err) } s.log.Logf("[INFO] TCP 服务监听 %s", addr) go func() { <-s.ctx.Done() listener.Close() }() for { conn, err := listener.Accept() if err != nil { select { case <-s.ctx.Done(): return nil default: s.log.Logf("[ERROR] Accept 失败: %v", err) continue } } go s.handleConn(conn) } } func (s *TCPServer) handleConn(conn net.Conn) { defer conn.Close() remoteAddr := conn.RemoteAddr().String() s.log.Logf("[INFO] 新连接: %s", remoteAddr) reader := bufio.NewReader(conn) for { select { case <-s.ctx.Done(): return default: } line, err := reader.ReadString('>') if err != nil { if err != io.EOF { s.log.Logf("[WARN] 读取 %s 错误: %v", remoteAddr, err) } s.log.Logf("[INFO] 连接断开: %s", remoteAddr) return } cmd := strings.TrimSpace(line) switch cmd { case "<1,0,post>": s.log.Logf("[INFO] <<< 收到命令 [%s]: %s 关灯命令", remoteAddr, cmd) go s.handleCmd1_0() case "<1,1,post>": s.log.Logf("[INFO] <<< 收到命令 [%s]: %s 开灯命令", remoteAddr, cmd) go s.handleCmd1_1() default: s.log.Logf("[WARN] 未知命令: %s", cmd) continue } } } // cancelAutoOffTimer 取消自动关灯定时器(如果有的话) func (s *TCPServer) cancelAutoOffTimer() { s.timerMu.Lock() defer s.timerMu.Unlock() if s.autoOffTimer != nil { s.autoOffTimer.Stop() s.autoOffTimer = nil s.log.Logf("[INFO] 已取消自动关灯定时器") } } // resetAutoOffTimer 重置自动关灯定时器(先取消旧的,再创建新的) func (s *TCPServer) resetAutoOffTimer() { s.timerMu.Lock() if s.autoOffTimer != nil { s.autoOffTimer.Stop() } delay := time.Duration(s.cfg.ResendDelaySeconds) * time.Second msg := s.effectiveMsg("<1,1,post>") s.log.Logf("[INFO] 重置自动关灯定时器: %v 后发送 %s", delay, msg) s.autoOffTimer = time.AfterFunc(delay, func() { s.timerMu.Lock() s.autoOffTimer = nil s.timerMu.Unlock() s.log.Logf("[INFO] 自动关灯触发: 发送 %s", msg) if err := s.sendMsg("<1,1,post>"); err != nil { s.log.Logf("[ERROR] 自动关灯发送失败: %v", err) } }) s.timerMu.Unlock() } // handleCmd1_0: 收到 <1,0,post>(关灯命令)→ 远端发送 <1,1,post>,并取消自动关灯定时器 func (s *TCPServer) handleCmd1_0() { s.cancelAutoOffTimer() // 关灯了就不用再自动关了 s.log.Logf("[INFO] 处理 <1,0,post>: 向远端发送 %s", s.effectiveMsg("<1,1,post>")) if err := s.sendMsg("<1,1,post>"); err != nil { s.log.Logf("[ERROR] 发送失败: %v", err) } } func (s *TCPServer) effectiveMsg(msg string) string { if s.reverse { return flipMsg(msg) } return msg } // handleCmd1_1: 收到 <1,1,post>(开灯命令)→ 远端发送 <1,0,post>,重置自动关灯定时器 func (s *TCPServer) handleCmd1_1() { s.log.Logf("[INFO] 处理 <1,1,post>: 向远端发送 %s", s.effectiveMsg("<1,0,post>")) if err := s.sendMsg("<1,0,post>"); err != nil { s.log.Logf("[ERROR] 发送失败: %v", err) } // 重置自动关灯定时器(如果之前有旧的会被取消) s.resetAutoOffTimer() } // ==================== 主函数 ==================== func main() { configPath := "config.json" if len(os.Args) > 1 { configPath = os.Args[1] } cfg, err := loadConfig(configPath) if err != nil { log.Fatalf("[FATAL] %v", err) } // 初始化按天滚动日志 dailyLog := NewDailyLogger(cfg.Log.Dir, cfg.Log.Level, cfg.Log.KeepDays) dailyLog.Logf("[INFO] ======== TCP 转发服务启动 ========") dailyLog.Logf("[INFO] 配置: 监听端口=%d, 远端=%s:%d, 启动延迟=%ds, 重发延迟=%ds, 正反=%v, 日志保留=%d天", cfg.Server.Port, cfg.Remote.Host, cfg.Remote.Port, cfg.Timing.StartupDelaySeconds, cfg.Timing.ResendDelaySeconds, cfg.Reverse, cfg.Log.KeepDays) // 创建远端 TCP 客户端 remoteClient := NewTCPClient(cfg.Remote.Host, cfg.Remote.Port, cfg.Remote.DialTimeout, dailyLog) defer remoteClient.Close() // 启动时连接远端 if err := remoteClient.Connect(); err != nil { dailyLog.Logf("[WARN] 启动时连接远端失败: %v (将在收到命令时自动重连)", err) } // 启动延迟 → 自动发送 <1,1,post>(正模式时翻转) startupDelay := time.Duration(cfg.Timing.StartupDelaySeconds) * time.Second startupMsg := flipMsgCond("<1,1,post>", cfg.Reverse) dailyLog.Logf("[INFO] 将在 %v 后自动发送 %s", startupDelay, startupMsg) time.AfterFunc(startupDelay, func() { dailyLog.Logf("[INFO] 启动定时触发: 发送 %s", startupMsg) if err := remoteClient.Send(startupMsg); err != nil { dailyLog.Logf("[ERROR] 启动定时发送失败: %v", err) } }) // 启动 TCP 服务端 server := NewTCPServer(cfg.Server.Port, remoteClient, &cfg.Timing, cfg.Reverse, dailyLog) // 优雅关闭 go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh dailyLog.Logf("[INFO] 收到退出信号,正在关闭...") shutdownMsg := flipMsgCond("<1,1,post>", cfg.Reverse) dailyLog.Logf("[INFO] 退出前发送 %s", shutdownMsg) if err := remoteClient.Send(shutdownMsg); err != nil { dailyLog.Logf("[ERROR] 退出发送失败: %v", err) } server.cancel() remoteClient.Close() os.Exit(0) }() if err := server.Start(); err != nil { dailyLog.Logf("[FATAL] 服务启动失败: %v", err) os.Exit(1) } }