293 lines
7.3 KiB
Go
293 lines
7.3 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// 客户端信息结构
|
||
type ClientInfo struct {
|
||
IP string // IP地址
|
||
Port string // 端口
|
||
LastSeen time.Time // 最后活跃时间
|
||
}
|
||
|
||
// 客户端列表(使用互斥锁保护的映射)
|
||
var (
|
||
clientsMutex sync.Mutex
|
||
clients = make(map[string]*ClientInfo)
|
||
)
|
||
|
||
// StartTCPServer 启动TCP服务器
|
||
func StartTCPServer(address string) error {
|
||
listener, err := net.Listen("tcp", address)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
startClientCleanup()
|
||
|
||
Logger.Printf("TCP服务器已启动,正在监听 %s\n", address)
|
||
|
||
for {
|
||
conn, err := listener.Accept()
|
||
if err != nil {
|
||
Logger.Printf("接受连接失败: %v", err)
|
||
continue
|
||
}
|
||
|
||
go handleConnection(conn)
|
||
}
|
||
}
|
||
|
||
// handleConnection 处理客户端连接
|
||
func handleConnection(conn net.Conn) {
|
||
defer conn.Close()
|
||
|
||
remoteAddr := conn.RemoteAddr().String()
|
||
Logger.Printf("新的客户端连接: %s", remoteAddr)
|
||
|
||
addClient(remoteAddr)
|
||
|
||
// 启动定时发送指令的goroutine
|
||
go sendPeriodicCommand(conn, remoteAddr)
|
||
|
||
buffer := make([]byte, 1024)
|
||
|
||
for {
|
||
n, err := conn.Read(buffer)
|
||
if err != nil {
|
||
if err != io.EOF {
|
||
Logger.Printf("从客户端读取失败 %s: %v", remoteAddr, err)
|
||
} else {
|
||
Logger.Printf("客户端断开连接 %s", remoteAddr)
|
||
}
|
||
removeClient(remoteAddr)
|
||
break
|
||
}
|
||
|
||
rawData := string(buffer[:n])
|
||
TCPDataLogger.Printf("从客户端 %s 接收到原始数据: %s", remoteAddr, rawData)
|
||
|
||
sensorID, x, y, z, temperature, err := parseData(rawData)
|
||
|
||
if err == nil {
|
||
TCPDataLogger.Printf("解析成功 - 客户端: %s, 传感器ID: %d, 值: X=%.3f, Y=%.3f, Z=%.3f, 温度=%.1f°C",
|
||
remoteAddr, sensorID, x, y, z, temperature)
|
||
|
||
if err := SaveSensorData(sensorID, x, y, z, temperature); err != nil {
|
||
Logger.Printf("保存传感器数据失败: %v", err)
|
||
}
|
||
} else {
|
||
TCPDataLogger.Printf("无法解析从客户端 %s 接收到的数据: %s, 错误: %v", remoteAddr, rawData, err)
|
||
}
|
||
|
||
resp := "OK\n"
|
||
if _, err := conn.Write([]byte(resp)); err != nil {
|
||
Logger.Printf("发送响应到客户端 %s 失败: %v", remoteAddr, err)
|
||
removeClient(remoteAddr)
|
||
break
|
||
}
|
||
|
||
updateClientLastSeen(remoteAddr)
|
||
}
|
||
}
|
||
|
||
// sendPeriodicCommand 每10秒发送一次查询指令
|
||
func sendPeriodicCommand(conn net.Conn, remoteAddr string) {
|
||
ticker := time.NewTicker(10 * time.Second)
|
||
defer ticker.Stop()
|
||
|
||
for {
|
||
select {
|
||
case <-ticker.C:
|
||
command := "@1602301014A*0!\n"
|
||
if _, err := conn.Write([]byte(command)); err != nil {
|
||
Logger.Printf("发送定时指令到客户端 %s 失败: %v", remoteAddr, err)
|
||
return // 连接断开,退出goroutine
|
||
}
|
||
TCPDataLogger.Printf("发送定时指令到客户端 %s: %s", remoteAddr, strings.TrimSpace(command))
|
||
}
|
||
}
|
||
}
|
||
|
||
// parseData 使用正则表达式解析传感器数据,支持新格式 #{1602301014-01,1,1,28.4,-6.884,1.540}!
|
||
func parseData(data string) (int, float64, float64, float64, float64, error) {
|
||
// 尝试解析新格式: #{1602301014-01,1,1,28.4,-6.884,1.540}!
|
||
newPattern := regexp.MustCompile(`#\{[^,]+-(\d+),\d+,(\d+),([-]?\d+\.\d+),([-]?\d+\.\d+),([-]?\d+\.\d+)\}!`)
|
||
matches := newPattern.FindStringSubmatch(data)
|
||
|
||
if len(matches) == 6 {
|
||
// 新格式解析
|
||
sensorID, err := strconv.Atoi(matches[2]) // 使用传感器地址编号
|
||
if err != nil {
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("解析传感器ID失败: %v", err)
|
||
}
|
||
|
||
temperature, err := strconv.ParseFloat(strings.TrimSpace(matches[3]), 64)
|
||
if err != nil {
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("解析温度值失败: %v", err)
|
||
}
|
||
|
||
x, err := strconv.ParseFloat(strings.TrimSpace(matches[4]), 64)
|
||
if err != nil {
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("解析X值失败: %v", err)
|
||
}
|
||
|
||
y, err := strconv.ParseFloat(strings.TrimSpace(matches[5]), 64)
|
||
if err != nil {
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("解析Y值失败: %v", err)
|
||
}
|
||
|
||
z := 0.0 // 新格式没有Z值,设为0
|
||
|
||
return sensorID, x, y, z, temperature, nil
|
||
}
|
||
|
||
// 尝试解析旧格式: 1:1.000, 2.000, 3.000
|
||
oldPattern := regexp.MustCompile(`(\d+):([-]?\d+\.\d+),\s*([-]?\d+\.\d+),\s*([-]?\d+\.\d+)`)
|
||
matches = oldPattern.FindStringSubmatch(data)
|
||
|
||
if len(matches) == 5 {
|
||
// 旧格式解析
|
||
sensorID, err := strconv.Atoi(matches[1])
|
||
if err != nil {
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("解析传感器ID失败: %v", err)
|
||
}
|
||
|
||
x, err := strconv.ParseFloat(strings.TrimSpace(matches[2]), 64)
|
||
if err != nil {
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("解析X值失败: %v", err)
|
||
}
|
||
|
||
y, err := strconv.ParseFloat(strings.TrimSpace(matches[3]), 64)
|
||
if err != nil {
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("解析Y值失败: %v", err)
|
||
}
|
||
|
||
z, err := strconv.ParseFloat(strings.TrimSpace(matches[4]), 64)
|
||
if err != nil {
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("解析Z值失败: %v", err)
|
||
}
|
||
|
||
temperature := 0.0 // 旧格式没有温度值,设为0
|
||
|
||
return sensorID, x, y, z, temperature, nil
|
||
}
|
||
|
||
return 0, 0, 0, 0, 0, fmt.Errorf("数据格式不正确: %s", data)
|
||
}
|
||
|
||
// addClient 添加客户端
|
||
func addClient(addr string) {
|
||
clientsMutex.Lock()
|
||
defer clientsMutex.Unlock()
|
||
|
||
host, port, err := net.SplitHostPort(addr)
|
||
if err != nil {
|
||
Logger.Printf("解析客户端地址失败 %s: %v", addr, err)
|
||
host = addr
|
||
port = "unknown"
|
||
}
|
||
|
||
clients[addr] = &ClientInfo{
|
||
IP: host,
|
||
Port: port,
|
||
LastSeen: time.Now(),
|
||
}
|
||
|
||
Logger.Printf("添加新客户端: %s", addr)
|
||
}
|
||
|
||
// updateClientLastSeen 更新客户端最后活跃时间
|
||
func updateClientLastSeen(addr string) {
|
||
clientsMutex.Lock()
|
||
defer clientsMutex.Unlock()
|
||
|
||
if client, exists := clients[addr]; exists {
|
||
client.LastSeen = time.Now()
|
||
}
|
||
}
|
||
|
||
// removeClient 移除客户端
|
||
func removeClient(addr string) {
|
||
clientsMutex.Lock()
|
||
defer clientsMutex.Unlock()
|
||
|
||
if client, exists := clients[addr]; exists {
|
||
client.LastSeen = time.Now()
|
||
Logger.Printf("客户端标记为断开连接: %s", addr)
|
||
}
|
||
}
|
||
|
||
// getAllClients 获取所有客户端信息
|
||
func getAllClients() []map[string]interface{} {
|
||
clientsMutex.Lock()
|
||
defer clientsMutex.Unlock()
|
||
|
||
now := time.Now()
|
||
result := make([]map[string]interface{}, 0, len(clients))
|
||
|
||
for addr, client := range clients {
|
||
lastSeenDuration := now.Sub(client.LastSeen)
|
||
|
||
if lastSeenDuration > 24*time.Hour {
|
||
delete(clients, addr)
|
||
continue
|
||
}
|
||
|
||
isOnline := lastSeenDuration < 10*time.Minute
|
||
|
||
result = append(result, map[string]interface{}{
|
||
"address": addr,
|
||
"ip": client.IP,
|
||
"port": client.Port,
|
||
"lastSeen": client.LastSeen,
|
||
"isOnline": isOnline,
|
||
"lastSeenFormatted": formatDuration(lastSeenDuration),
|
||
})
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// formatDuration 格式化持续时间为友好的字符串
|
||
func formatDuration(d time.Duration) string {
|
||
if d < time.Minute {
|
||
return "刚刚"
|
||
} else if d < time.Hour {
|
||
return fmt.Sprintf("%d分钟前", int(d.Minutes()))
|
||
} else if d < 24*time.Hour {
|
||
return fmt.Sprintf("%d小时前", int(d.Hours()))
|
||
} else {
|
||
return fmt.Sprintf("%d天前", int(d.Hours()/24))
|
||
}
|
||
}
|
||
|
||
// startClientCleanup 启动清理过期客户端的goroutine
|
||
func startClientCleanup() {
|
||
go func() {
|
||
for {
|
||
time.Sleep(1 * time.Hour) // 每小时检查一次
|
||
|
||
clientsMutex.Lock()
|
||
now := time.Now()
|
||
|
||
for addr, client := range clients {
|
||
if now.Sub(client.LastSeen) > 24*time.Hour {
|
||
delete(clients, addr)
|
||
Logger.Printf("移除过期客户端: %s", addr)
|
||
}
|
||
}
|
||
|
||
clientsMutex.Unlock()
|
||
}
|
||
}()
|
||
}
|