文件内容采集-go

需求背景

1、多个线程写文件,并且在达到文件大小限制时进行文件绕接,但是绕接文件不压缩

2、另起一个线程对文件内容进行采集,然后将内容统一归档到指定路径

写文件客户端

package collectFile

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"time"
)

func WriteClientRun() {
	serviceNamePrefix := "svc-"
	for i := 0; i < 10; i++ {
		go writeFile(serviceNamePrefix + strconv.Itoa(i))
	}
}

func writeFile(serviceName string) {
	for {
		doWriteFile(serviceName)
	}
}

func doWriteFile(serviceName string) {
	dirPath := filepath.Join("D:\\tmp\\alarm_log", serviceName, BaseParent)
	_, dirStatErr := os.Stat(dirPath)
	if os.IsNotExist(dirStatErr) {
		dirCreateStatErr := os.MkdirAll(dirPath, 0777)
		if dirCreateStatErr != nil {
			fmt.Println("dirCreateStatErr ==> ", dirCreateStatErr.Error())
			return
		}
	}
	filePath := filepath.Join("D:\\tmp\\alarm_log", serviceName, BaseParent, serviceName+".wlog")
	file, OpenFileErr := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if OpenFileErr != nil {
		if OpenFileErr != nil {
			fmt.Println("OpenFileErr ==> ", OpenFileErr.Error())
		}
		return
	}
	stat, fileStatErr := file.Stat()
	if fileStatErr != nil {
		fmt.Println("fileStatErr ==> ", fileStatErr.Error())
		return
	}
	if stat.Size() > 1024 {
		// 在绕接前必须关闭文件流避免无法绕接时句柄冲突
		fileCloseErr := file.Close()
		if fileCloseErr != nil {
			fmt.Println("fileCloseErr when rolling ==> ", fileCloseErr.Error())
			return
		}

		// 进行绕接,并且生成新的文件用于记录信息
		formatTime := time.Now().Format("20060102150405")
		rollingFilePath := filepath.Join("D:\\tmp\\alarm_log", serviceName, BaseParent, serviceName+"_"+formatTime+".wlog")
		renameErr := os.Rename(filePath, rollingFilePath)
		if renameErr != nil {
			fmt.Println("renameErr when rolling ==> ", renameErr.Error())
			return
		}
		file, OpenFileErr = os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
		if OpenFileErr != nil {
			fmt.Println("OpenFileErr when rolling ==> ", OpenFileErr.Error())
			return
		}
	}
	defer func() {
		deferCloseErr := file.Close()
		if deferCloseErr != nil {
			fmt.Println("deferCloseErr ==> ", deferCloseErr.Error())
		}
	}()
	writer := bufio.NewWriter(file)
	_, writeStringErr := writer.WriteString("<<<" + serviceName + "-->" + time.Now().Format("15:04:05") + ">>>" + "\r\n")
	if writeStringErr != nil {
		fmt.Println("writeStringErr ==> ", writeStringErr.Error())
		return
	}

	flushErr := writer.Flush()
	if flushErr != nil {
		fmt.Println("flushErr ==> ", flushErr.Error())
		return
	}

	time.Sleep(time.Second.Round(10))
}

采集文件内容

采集时的缓存记录

package collectFile

import "sync"

type CursorInfo struct {
	// 已读位置记录
	offset int64
	// 被读取过的文件
	source string
	// 更新时间
	updateTime int64
}

// key: 服务名 val: 对应的文件已读信息CursorInfo
var cursorInfoMap sync.Map

// 更新缓存
func updateCache(newCursorInfoMap map[string]CursorInfo) {
	for serviceName, newCursorInfo := range newCursorInfoMap {
		cursorInfoMap.Store(serviceName, newCursorInfo)
	}
}

// 从缓存中获取结果
func getCursorInfo(serviceName string) CursorInfo {
	value, ok := cursorInfoMap.Load(serviceName)
	if !ok {
		return CursorInfo{}
	}
	return value.(CursorInfo)
}

// TODO 启动协程进行缓存同步持久化
func syncCursorInfo() {
	// 扫描磁盘存在日志的服务名称列表
	dirs := scanTargetDir()
	serviceNames := []string{}
	for _, dir := range dirs {
		_, serviceName := scanFilePath(dir)
		serviceNames = append(serviceNames, serviceName)
	}
	// 不存在的服务则直接剔除出缓存
	//cursorInfoMap.LoadAndDelete()

	// 将缓存信息备份持久化的本地磁盘

}

工具类

package collectFile

import (
	"bufio"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

const (
	// BaseDir 文件扫描的基础路径
	BaseDir = "D:\\tmp"
	// BaseParent 父目录名称
	BaseParent = "warning"
	// SuffixName 文件后缀
	SuffixName = "*.wlog"
	// 每条记录的最大值
	maxLen = 40 * 1024
	// 待读取内容前缀
	prefix = "<<<"
	// 待读取内容后缀
	suffix = ">>>"
)

var (
	rollingFileCompile, _ = regexp.Compile("^([\\s\\S]+)_[0-9]{14}.wlog$")
	fileCompile, _        = regexp.Compile("^([\\s\\S]+).wlog$")
)

func scanTargetDir() (dirs []string) {
	err := filepath.WalkDir(BaseDir, func(path string, d fs.DirEntry, err error) error {
		if !d.IsDir() {
			return nil
		}
		_, fileName := filepath.Split(path)
		if fileName == BaseParent {
			dirs = append(dirs, path)
		}
		return nil
	})
	if err != nil {
		fmt.Println("scanTargetDir err ==> ", err.Error())
		return []string{}
	}
	return
}

func scanFilePath(dir string) (filePaths []string, serviceName string) {
	filePaths, err := filepath.Glob(filepath.Join(dir, string(filepath.Separator), SuffixName))
	if err != nil || len(filePaths) == 0 {
		fmt.Println("scanFilePath ==> ", err.Error())
		return []string{}, ""
	}
	sort.Slice(filePaths, func(i, j int) bool {
		return pathCompare(filePaths[i], filePaths[j]) < 0
	})
	// 截取服务名
	_, fileName := filepath.Split(filePaths[0])
	if rollingFileCompile.MatchString(fileName) {
		serviceName = rollingFileCompile.FindStringSubmatch(fileName)[1]
		return
	}
	if fileCompile.MatchString(fileName) {
		serviceName = fileCompile.FindStringSubmatch(fileName)[1]
		return
	}
	return
}

func isRollingFile(filePath string) bool {
	_, fileName := filepath.Split(filePath)
	return rollingFileCompile.MatchString(fileName)
}

// 通过文件路径名称比较文件新旧
// firstPath比secPath新,则返回-1
// firstPath比secPath旧,则返回1
// firstPath和secPath相同,则返回0
// 文件新旧关系与文件名强相关
// 例如:aad\warning\aad_20231130114550.wlog > aad\warning\aad_20231131124550.wlog > aad\warning\aad.wlog
func pathCompare(firstPath string, secPath string) int {
	firstPathLen := len(firstPath)
	secPathLen := len(secPath)
	if firstPathLen == 0 || secPathLen == 0 || firstPathLen == secPathLen {
		return strings.Compare(firstPath, secPath)
	}
	if firstPathLen < secPathLen {
		return 1
	}
	if firstPathLen > secPathLen {
		return -1
	}
	return 0
}

// 读取文件内容
func readFileInfo(filePath string, startOffSet int64) (fileInfos []string, newOffset int64) {
	file, fileOpenErr := os.Open(filePath)
	if fileOpenErr != nil {
		fmt.Println("readFileInfo fileOpenErr ===> ", fileOpenErr.Error())
		return
	}
	defer file.Close()
	_, seekErr := file.Seek(startOffSet, io.SeekCurrent)
	if seekErr != nil {
		fmt.Println("readFileInfo seekErr ===> ", seekErr.Error())
		return
	}
	// 初始化定位
	newOffset = startOffSet
	scanner := bufio.NewScanner(file)
	scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		var newFileInfos []string
		advance, newFileInfos = parseFileInfo(data)
		fileInfos = append(fileInfos, newFileInfos...)
		newOffset += int64(advance)
		return
	})

	for scanner.Scan() {
	}

	return
}

func parseFileInfo(data []byte) (offset int, fileInfos []string) {
	// 临时缓存'<<<'和'>>>' 之间的数据
	var dataBuf []byte
	// 开始记录数据的标识
	var startFlag bool
	for index, c := range data {
		if startFlag && c == '>' {
			// 如果前两位都是 '>', 并且startFlag == true 则说明读取完成
			dataBuf = append(dataBuf, c)
			if index > 1 &&
				data[index-1] == '>' && data[index-2] == '>' {
				dataBuf = dataBuf[:len(dataBuf)-3]
				if len(dataBuf) != 0 {
					fileInfos = append(fileInfos, string(dataBuf))
				}
				startFlag = false
				dataBuf = []byte{}
			}
			continue
		}
		if startFlag {
			dataBuf = append(dataBuf, c)
			continue
		}
		if !startFlag && c == '<' {
			// 如果前两位都是 '<', 则将标识位startFlag置为true,后续则开始记录数据
			if index > 1 &&
				data[index-1] == '<' && data[index-2] == '<' {
				startFlag = true
			}
		}
	}
	// 如果不存在dataBuf,则说明data的数据都符合格式,需要直接删除
	if len(dataBuf) != 0 {
		offset = len(data) - (len(dataBuf) + 3)
	} else {
		offset = len(data)
	}
	return
}

采集文件

package collectFile

import (
	"bufio"
	"fmt"
	"os"
	"time"
)

func ScanFile() {
	// 扫描目录
	for {
		dirs := scanTargetDir()
		if len(dirs) == 0 {
			time.Sleep(time.Second * 5)
			continue
		}
		collectFileInfo(dirs)
	}
}

func collectFileInfo(dirs []string) {
	newCursorInfoCache := make(map[string]CursorInfo)
	var fileInfos []string
	for _, dir := range dirs {
		// 获取当前目录下的所有告警日志文件名称,包含绕接文件和非绕接文件
		filePaths, serviceName := scanFilePath(dir)
		filePathsLen := len(filePaths)
		if filePathsLen == 0 {
			fmt.Println("collectFileInfo filePaths is empty ==> ", filePaths)
			continue
		}
		if len(filePaths) > 2 {
			filePaths = filePaths[filePathsLen-3:]
		}
		cursorInfo := getCursorInfo(serviceName)
		for _, filePath := range filePaths {
			unixMilli := time.Now().UnixMilli()
			fileStat, fileStatErr := os.Stat(filePath)
			if fileStatErr != nil {
				fmt.Println("collectFileInfo fileStateError ==> ", fileStatErr.Error())
				break
			}
			if fileStat.ModTime().UnixMilli() < cursorInfo.updateTime {
				// 说明已经被读取过,则直接跳过
				continue
			}
			if cursorInfo.offset >= fileStat.Size() && cursorInfo.source == filePath {
				cursorInfo.offset = 0
			}
			newFileInfos, newOffSet := readFileInfo(filePath, cursorInfo.offset)
			// 缓存游标信息
			newCursorInfo := CursorInfo{newOffSet, filePath, unixMilli}
			fileInfos = append(fileInfos, newFileInfos...)
			newCursorInfoCache[serviceName] = newCursorInfo
			// 重置游标
			cursorInfo.offset = newOffSet
			if len(fileInfos) >= 100 {
				// 将扫描内容归档到新的文件中,此处用于模拟采集的数据归档操作
				recordInfo(fileInfos)
				// 归档完成之后,清理临时缓存
				fileInfos = []string{}
				// 刷新游标缓存信息,并且更新缓存
				updateCache(newCursorInfoCache)
				newCursorInfoCache = make(map[string]CursorInfo)
				// 等待5s再进行后续扫描
				time.Sleep(time.Second * 5)
				// 此时最好跳过当前服务的文件扫描,避免又有绕接文件,同时避免后面的服务饥饿的问题
				break
			}
		}
	}
	if len(fileInfos) > 0 {
		// 将扫描内容归档到新的文件中,此处用于模拟采集的数据归档
		recordInfo(fileInfos)
		// 刷新游标缓存信息
		updateCache(newCursorInfoCache)
	}
	// 等待5s再进行后续扫描
	time.Sleep(time.Second * 5)
}

func recordInfo(infos []string) {
	file, OpenFileErr := os.OpenFile("D:\\tmp\\alarm_log\\all.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if OpenFileErr != nil {
		fmt.Println("recordInfo OpenFileErr ==> ", OpenFileErr.Error())
		return
	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	for _, info := range infos {
		writer.WriteString(info + "\r\n")
	}
	writer.Flush()
}

文件内容采集结果正确性校验

package collectFile

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"
)

func CheckFileInfo() {
	allMap := getInfo("D:\\tmp\\alarm_log\\all.txt")
	for i := 0; i < 10; i++ {
		join := filepath.Join("D:\\tmp\\alarm_log\\svc-"+strconv.Itoa(i), "warning", "*.wlog")
		matchePaths, _ := filepath.Glob(join)
		for _, path := range matchePaths {
			infoMap := getInfo(path)
			// 校验allMap中是否都存在infoMap的内容
			for key, _ := range infoMap {
				key = key[3 : len(key)-3]
				if allMap[key] != key {
					fmt.Println("allMap not exist : ", key)
				}
			}
		}
	}

}

func getInfo(path string) map[string]string {
	allMap := make(map[string]string)
	file, openFileErr := os.Open(path)
	if openFileErr != nil {
		return allMap
	}

	defer file.Close()
	reader := bufio.NewReader(file)
	for {
		line, _, err := reader.ReadLine()
		if err == io.EOF {
			break
		}
		allMap[string(line)] = string(line)
	}
	return allMap
}

调用代码

	collectFile.WriteClientRun() // 写文件
	collectFile.ScanFile() // 采集文件

// 	collectFile.CheckFileInfo() 进行文件内容校验

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Go 语言中实现一个串口 Modbus 采集程序需要以下步骤: 1. 使用 Go 的 `github.com/tarm/serial` 库打开串口连接,并设置正确的波特率和其他串口参数。 2. 将 Modbus 功能码和寄存器地址编码成 Modbus RTU 报文。 3. 通过串口连接发送 Modbus RTU 报文并接收响应。 4. 解码 Modbus 响应报文,并检查响应是否有错误。 5. 如果响应没有错误,则解析响应中的数据并使用它们。 以下是一个简单的 Go 程序示例,它实现了读取 Modbus 从机寄存器的功能: ``` package main import ( "fmt" "github.com/tarm/serial" "time" ) func main() { // 打开串口连接 c := &serial.Config{Name: "/dev/ttyUSB0", Baud: 9600} s, err := serial.OpenPort(c) if err != nil { fmt.Println(err) return } defer s.Close() // 编码 Modbus RTU 请求报文 request := []byte{1, 3, 0, 0, 0, 2} // 功能码 3:读寄存器;寄存器地址:0;读取数量:2 crc := calculateCRC(request) // 计算 Modbus RTU 报文的 CRC 校验和 request = append(request, crc[0], crc[1]) // 发送 Modbus RTU 请求并接收响应 _, err = s.Write(request) if err != nil { fmt.Println(err) return } buf := make([]byte, 256) n, err := s. ### 回答2: 使用Go语言实现一个串口Modbus的采集程序可以按照以下步骤进行: 1. 导入相关的包:首先需要导入serial包和modbus包,可以使用go get命令进行安装。 ```go import ( "fmt" "github.com/jacobsa/go-serial/serial" "github.com/goburrow/modbus" ) ``` 2. 设置串口连接:使用serial.Open函数设置串口连接的参数,如端口号、波特率等。 ```go port := "/dev/ttyUSB0" // 串口号 baudRate := 9600 // 波特率 options := serial.OpenOptions{ PortName: port, BaudRate: baudRate, DataBits: 8, StopBits: 1, MinimumReadSize: 4, } // 打开串口 serialPort, err := serial.Open(options) if err != nil { fmt.Println("Failed to open serial port:", err) return } defer serialPort.Close() ``` 3. 创建Modbus客户端:使用modbus.NewRTUClient函数创建Modbus客户端。 ```go handler := modbus.NewRTUClientHandler(serialPort) handler.BaudRate = baudRate handler.DataBits = 8 handler.Parity = "N" handler.StopBits = 1 handler.Timeout = time.Second // 设置超时时间 err = handler.Connect() if err != nil { fmt.Println("Failed to connect to Modbus:", err) return } defer handler.Close() client := modbus.NewClient(handler) ``` 4. 读取Modbus寄存器:使用client.ReadHoldingRegisters函数读取Modbus寄存器的值。 ```go address := uint16(0) // 寄存器地址 length := uint16(1) // 寄存器数量 results, err := client.ReadHoldingRegisters(address, length) if err != nil { fmt.Println("Failed to read Modbus registers:", err) return } fmt.Println("Register value:", results[0]) ``` 通过以上步骤,我们就可以实现一个使用Go语言实现串口Modbus的采集程序。其中,需要注意设置正确的串口参数和寄存器地址,控制错误的处理和连接的关闭。 ### 回答3: 要使用Go语言实现一个串口Modbus的采集程序,首先需要安装Go语言的开发环境。接下来,可以通过在终端中输入以下命令来安装相关的库: ``` go get github.com/goburrow/modbus ``` 接下来,可以创建一个新的Go程序文件,并导入必要的库: ```go package main import ( "fmt" "log" "github.com/goburrow/modbus" ) func main() { client := modbus.NewRTUClientHandler("COM1") // 替换为实际的串口号 client.BaudRate = 9600 // 设置波特率 client.DataBits = 8 // 设置数据位数 client.Parity = "None" // 设置校验位 client.StopBits = 1 // 设置停止位 err := client.Connect() if err != nil { log.Fatal(err) } defer client.Close() // 读取保持寄存器 results, err := client.ReadHoldingRegisters(1, 10) // 读取地址为1的保持寄存器,读取10个寄存器数据 if err != nil { log.Fatal(err) } fmt.Printf("Read results: %v\n", results) } ``` 上述代码使用了"goburrow/modbus"库来实现Modbus协议的读取功能。首先,创建一个新的RTU(串口)ClientHandler,将串口号和配置参数传入。然后通过Connect()方法建立与设备的连接,最后通过ReadHoldingRegisters()方法读取指定地址的保持寄存器数据。 注意:在实际使用中,需要将上述代码中的"COM1"替换为实际的串口号,并根据实际需求进行配置。 总之,使用Go语言可以很方便地实现串口Modbus的采集程序,只需安装相关库并编写相应的代码即可实现读取功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值