382 lines
10 KiB
Go
382 lines
10 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)
|
||
clientConns = make(map[string]net.Conn) // 存储客户端连接
|
||
)
|
||
|
||
// 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)
|
||
|
||
// 存储连接以便手动查询使用
|
||
clientsMutex.Lock()
|
||
clientConns[remoteAddr] = conn
|
||
clientsMutex.Unlock()
|
||
|
||
// 注释掉自动发送指令 - 设备刚连接时立即发送一次查询指令
|
||
go func() {
|
||
time.Sleep(2 * time.Second) // 等待2秒让连接稳定
|
||
command := "@1602301014A*0!\n"
|
||
if _, err := conn.Write([]byte(command)); err != nil {
|
||
Logger.Printf("发送连接后查询指令到客户端 %s 失败: %v", remoteAddr, err)
|
||
} else {
|
||
TCPDataLogger.Printf("发送连接后查询指令到客户端 %s: %s", remoteAddr, strings.TrimSpace(command))
|
||
}
|
||
}()
|
||
|
||
// 启动定时发送指令的goroutine(30分钟间隔)
|
||
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)
|
||
// 清理连接映射
|
||
clientsMutex.Lock()
|
||
delete(clientConns, remoteAddr)
|
||
clientsMutex.Unlock()
|
||
break
|
||
}
|
||
|
||
rawData := string(buffer[:n])
|
||
TCPDataLogger.Printf("从客户端 %s 接收到原始数据: %s", remoteAddr, rawData)
|
||
|
||
// 注释掉心跳包响应 - 检查是否为心跳包 JML610
|
||
// if strings.Contains(rawData, "JML610") {
|
||
// TCPDataLogger.Printf("收到心跳包从客户端 %s: %s", remoteAddr, rawData)
|
||
//
|
||
// // 立即发送查询指令
|
||
// command := "@1602301014A*0!\n"
|
||
// if _, err := conn.Write([]byte(command)); err != nil {
|
||
// Logger.Printf("响应心跳包发送查询指令到客户端 %s 失败: %v", remoteAddr, err)
|
||
// } else {
|
||
// TCPDataLogger.Printf("响应心跳包发送查询指令到客户端 %s: %s", remoteAddr, strings.TrimSpace(command))
|
||
// }
|
||
//
|
||
// updateClientLastSeen(remoteAddr)
|
||
// continue // 跳过数据解析,继续监听
|
||
// }
|
||
|
||
// 检查是否为心跳包 JML610(仅记录,不发送查询指令)
|
||
if strings.Contains(rawData, "JML610") {
|
||
TCPDataLogger.Printf("收到心跳包从客户端 %s: %s", remoteAddr, rawData)
|
||
updateClientLastSeen(remoteAddr)
|
||
|
||
// 发送OK响应
|
||
resp := "OK\n"
|
||
if _, err := conn.Write([]byte(resp)); err != nil {
|
||
Logger.Printf("发送响应到客户端 %s 失败: %v", remoteAddr, err)
|
||
removeClient(remoteAddr)
|
||
// 清理连接映射
|
||
clientsMutex.Lock()
|
||
delete(clientConns, remoteAddr)
|
||
clientsMutex.Unlock()
|
||
break
|
||
}
|
||
continue // 跳过数据解析,继续监听
|
||
}
|
||
|
||
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)
|
||
// 清理连接映射
|
||
clientsMutex.Lock()
|
||
delete(clientConns, remoteAddr)
|
||
clientsMutex.Unlock()
|
||
break
|
||
}
|
||
|
||
updateClientLastSeen(remoteAddr)
|
||
}
|
||
}
|
||
|
||
// sendPeriodicCommand 每30分钟发送一次查询指令
|
||
func sendPeriodicCommand(conn net.Conn, remoteAddr string) {
|
||
ticker := time.NewTicker(30 * time.Minute)
|
||
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)
|
||
delete(clientConns, addr) // 同时清理连接
|
||
Logger.Printf("移除过期客户端: %s", addr)
|
||
}
|
||
}
|
||
|
||
clientsMutex.Unlock()
|
||
}
|
||
}()
|
||
}
|
||
|
||
// triggerManualQuery 手动触发向所有在线客户端发送查询指令
|
||
func triggerManualQuery() int {
|
||
clientsMutex.Lock()
|
||
defer clientsMutex.Unlock()
|
||
|
||
sentCount := 0
|
||
command := "@1602301014A*0!\n"
|
||
|
||
for addr, client := range clients {
|
||
// 检查客户端是否在线(最近10分钟内活跃)
|
||
if time.Since(client.LastSeen) < 10*time.Minute {
|
||
if conn, exists := clientConns[addr]; exists {
|
||
if _, err := conn.Write([]byte(command)); err != nil {
|
||
Logger.Printf("手动发送查询指令到客户端 %s 失败: %v", addr, err)
|
||
// 连接可能已断开,从映射中移除
|
||
delete(clientConns, addr)
|
||
} else {
|
||
TCPDataLogger.Printf("手动发送查询指令到客户端 %s: %s", addr, strings.TrimSpace(command))
|
||
sentCount++
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Logger.Printf("手动查询指令已发送到 %d 个客户端", sentCount)
|
||
return sentCount
|
||
}
|