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