使用interface化解一场因操作系统不同导致的编译问题
场景描述
起因:
因项目需求,需要编写一个agent, 需支持Linux和Windows操作系统。 Agent里面有一个功能需要获取到服务器上所有已经被占用的端口。
实现方式:针对不同的操作系统,实现方式有所不同
- linux: 使用服务器自带的
netstat指令,然后使用os/exec库来调用 shell脚本实现 - windows: windows系统不同在于,使用 exec.Command指令后,需要调用
syscall.SysProcAttr和syscall.LoadDLL, 而这两个方法是windows系统下的专用库。
问题: 这里会出出现一个问题,虽然程序在编译的时候可以通过GOOS来区分编译到指定的操作系统的二进制包, 但是在编译过程中,编译器会进行代码检查,也会加载windows的代码逻辑。
编译争端
初始代码如下:
- tools.go
// get address
func getAddress(addr string) string {
var address string
if strings.Contains(addr, "tcp") {
address = strings.TrimRight(addr, "tcp")
} else {
address = strings.TrimRight(addr, "udp")
}
return address
} // CollectServerUsedPorts, collect all of the ports that have been used
func CollectServerUsedPorts(platform string) string {
var (
platformLower = strings.ToLower(platform)
cmd *exec.Cmd
err error
cmdOutPut []byte
) if platformLower == "linux" {
// 执行 shell 指令, 获取tcp协议占用的端口
getUsedPortsCmd := `netstat -tln | awk '{print $4}' | awk -F: '{print $NF}' | egrep -o '[0-9]+' | sort -n | uniq | paste -s -d ","`
cmd = exec.Command("bash", "-c", getUsedPortsCmd) } else if platformLower == "windows" {
// 执行 powershell指令获取已经占用的端口号
getUsedPortsCmd := SelectScriptByWindowsVersion() cmd = exec.Command("powershell", "-Command", getUsedPortsCmd)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} } else {
cmd = nil
} if cmd != nil {
if cmdOutPut, err = cmd.Output(); err != nil {
log.Errorf("err to execute command %s, %s", cmd.String(), err.Error())
return ""
}
return strings.Trim(string(cmdOutPut), "\n")
}
return ""
} func SelectScriptByWindowsVersion() string {
var getUsedPortsCmd string
version, err := getWindowsVersion()
if err != nil {
log.Errorf("无法获取Windows版本信息: %s", err.Error())
return ""
} // if system version is lower than windows 8
if version < 6.2 {
log.Warnf("Windows 版本低于 Windows 8")
getUsedPortsCmd = `(netstat -an | Select-String 'LISTENING' | ForEach-Object { $_ -replace '\s+', ' ' } | ForEach-Object { ($_ -split ' ')[2] } | Where-Object {$_ -match '\d'} | ForEach-Object {[int]($_ -split ':')[-1]} | Sort-Object | Get-Unique ) -join ",".Replace("r","")`
} else {
getUsedPortsCmd = `((Get-NetTCPConnection | Where-Object {$_.State -eq 'Listen'} | Select-Object -ExpandProperty LocalPort) | Sort-Object {[int]$_} | Get-Unique) -join ",".Replace("r","")`
} return getUsedPortsCmd
} func getWindowsVersion() (float64, error) { mod, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
return 0, err
}
defer func() {
_ = mod.Release()
}() proc, err := mod.FindProc("GetVersion")
if err != nil {
return 0, err
} version, _, _ := proc.Call()
majorVersion := byte(version)
minorVersion := byte(version >> 8) return float64(majorVersion) + float64(minorVersion)/10, nil
}
- 上面代码编译成windows没问题,但是编译linux二进制文件时,会提示:
# 编译linux二进制文件
go build -ldflags "-linkmode external -extldflags '-static'" -tags musl -o main main.go # 错误输出如下
windows.go:31:22: undefined: syscall.LoadDLL
windows.go:56:41: unknown field 'HideWindow' in struct literal of type syscall.SysProcAttr # 错误原因
- 内置库syscall,在linux编译时,其syscall.SysProcAttr 结构体并没有`HideWindow`字段;
- linux 下也没有 syscall.LoadDLL方法
- 编译和代码执行逻辑不一样,虽然代码有检查系统服务器类型的逻辑,但是编译时需要加载代码中的每一行代码逻辑,
- 将其编译成汇编,然后再交给计算机执行,所以会出现编译错误
矛盾化解
Go语言在编译时除了有对整个项目编译的 参数控制 , 如 参数GOOS=windows表示编译成widnwos系统下的二进制文件。 但是这个参数只能控制项目级别的, 对于上面这种情况,需要控制文件级别的编译, 当然 Go也是支持的,在提取出 windows 逻辑的代码为独立文件,在文件开头使用 // + build windows 语法。修改如下:
used_ports/windows.go如下:// +build windows
func SelectScriptByWindowsVersion() string {
var getUsedPortsCmd string
version, err := getWindowsVersion()
if err != nil {
log.Errorf("无法获取Windows版本信息: %s", err.Error())
return ""
} // if system version is lower than windows 8
if version < 6.2 {
log.Warnf("Windows 版本低于 Windows 8")
getUsedPortsCmd = `(netstat -an | Select-String 'LISTENING' | ForEach-Object { $_ -replace '\s+', ' ' } | ForEach-Object { ($_ -split ' ')[2] } | Where-Object {$_ -match '\d'} | ForEach-Object {[int]($_ -split ':')[-1]} | Sort-Object | Get-Unique ) -join ",".Replace("r","")`
} else {
getUsedPortsCmd = `((Get-NetTCPConnection | Where-Object {$_.State -eq 'Listen'} | Select-Object -ExpandProperty LocalPort) | Sort-Object {[int]$_} | Get-Unique) -join ",".Replace("r","")`
} return getUsedPortsCmd
} func getWindowsVersion() (float64, error) { mod, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
return 0, err
}
defer func() {
_ = mod.Release()
}() proc, err := mod.FindProc("GetVersion")
if err != nil {
return 0, err
} version, _, _ := proc.Call()
majorVersion := byte(version)
minorVersion := byte(version >> 8) return float64(majorVersion) + float64(minorVersion)/10, nil
}
将原来逻辑改成如下:
windows.go// CollectServerUsedPorts, collect all of the ports that have been used
func CollectServerUsedPorts(platform string) string {
var (
platformLower = strings.ToLower(platform)
cmd *exec.Cmd
err error
cmdOutPut []byte
) if platformLower == "linux" {
// 执行 shell 指令, 获取tcp协议占用的端口
getUsedPortsCmd := `netstat -tln | awk '{print $4}' | awk -F: '{print $NF}' | egrep -o '[0-9]+' | sort -n | uniq | paste -s -d ","`
cmd = exec.Command("bash", "-c", getUsedPortsCmd) } else if platformLower == "windows" {
// todo: 需要优化,通过接口映射避免编译的问题
// Linux 编译时需要隐藏下面代码,
getUsedPortsCmd := used_ports.CollectWindowsUsedPorts()
cmd = exec.Command("powershell", "-Command", getUsedPortsCmd)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} } else {
cmd = nil
} if cmd != nil {
if cmdOutPut, err = cmd.Output(); err != nil {
log.Errorf("err to execute command %s, %s", cmd.String(), err.Error())
return ""
}
return strings.Trim(string(cmdOutPut), "\n")
}
return ""
}
光修改了上面的逻辑也不行,因为编译的时候代码依然会执行,此时会报如下错误
tools.go:121:15: undefined: used_ports.CollectWindowsUsedPorts # 这是因为虽然linux编译时,不会编译 windows.go的文件,同时会导致 模块下的
#CollectWindowsUsedPorts 方法不存在
最终修复方式如下
- 编译时考虑使用 定义接口的方式, 针对不同操作系统使用不同的 结构体,然后通过结构实现接口的方式来使其两种操作系统方法来指向同一个接口
- 使用 接口字典的方式,实现策略模式,不再使用显示的
if判断语法来做显示判断,这样可以避免编译时显示加载因操作系统带来的编译冲突 - 使用
init()方式初始化 接口实现 - 最终代码如下
/* 实现接口目录如下
├── used_ports
│ ├── linux.go
│ ├── used_ports.go
│ └── windows.go
*/
- used_ports.go
package used_ports import "os/exec" type UsedPortCollector interface {
CollectHaveUsedPorts() *exec.Cmd
} var UsedPortCollectorMap = make(map[string]UsedPortCollector, 2) func Register(platformOS string, collector UsedPortCollector) {
if _, ok := Find(platformOS); ok {
return
} UsedPortCollectorMap[platformOS] = collector
} func Find(platformOS string) (UsedPortCollector, bool) { c, ok := UsedPortCollectorMap[platformOS]
return c, ok
}
- linux.go
package used_ports import (
"os/exec"
) func init() {
Register("linux", newLinuxUsedPorts())
} type linuxPortCollectorImpl struct{} func (w linuxPortCollectorImpl) CollectHaveUsedPorts() *exec.Cmd {
// 执行 shell 指令, 获取tcp协议占用的端口
getUsedPortsCmd := `netstat -tln | awk '{print $4}' | awk -F: '{print $NF}' | egrep -o '[0-9]+' | sort -n | uniq | paste -s -d ","`
cmd := exec.Command("bash", "-c", getUsedPortsCmd)
return cmd
} // newLinuxUsedPorts 返回Windows系统下的端口收集器实例
func newLinuxUsedPorts() UsedPortCollector {
return linuxPortCollectorImpl{}
}
- windows.go
// +build windows package used_ports import (
"os/exec"
"syscall"
) func init() {
Register("windows", newWindowsCollector())
} // 结构体
type windowsPortCollectorImpl struct{} // 实现接口方法
func (w windowsPortCollectorImpl) CollectHaveUsedPorts() *exec.Cmd {
// 执行 powershell指令获取已经占用的端口号
getUsedPortsCmd := selectScriptByWindowsVersion() cmd := exec.Command("powershell", "-Command", getUsedPortsCmd)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} return cmd
} // newWindowsCollector 返回Windows系统下的端口收集器实例,算是工厂方法
func newWindowsCollector() UsedPortCollector {
return windowsPortCollectorImpl{}
} func selectScriptByWindowsVersion() string {
var getUsedPortsCmd string
version, err := getWindowsVersion()
if err != nil {
return ""
} // if system version is lower than windows 8
if version < 6.2 {
getUsedPortsCmd = `(netstat -an | Select-String 'LISTENING' | ForEach-Object { $_ -replace '\s+', ' ' } | ForEach-Object { ($_ -split ' ')[2] } | Where-Object {$_ -match '\d'} | ForEach-Object {[int]($_ -split ':')[-1]} | Sort-Object | Get-Unique ) -join ",".Replace("r","")`
} else {
getUsedPortsCmd = `((Get-NetTCPConnection | Where-Object {$_.State -eq 'Listen'} | Select-Object -ExpandProperty LocalPort) | Sort-Object {[int]$_} | Get-Unique) -join ",".Replace("r","")`
} return getUsedPortsCmd
} func getWindowsVersion() (float64, error) { mod, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
return 0, err
}
defer func() {
_ = mod.Release()
}() proc, err := mod.FindProc("GetVersion")
if err != nil {
return 0, err
} version, _, _ := proc.Call()
majorVersion := byte(version)
minorVersion := byte(version >> 8) return float64(majorVersion) + float64(minorVersion)/10, nil
}
tools.go... // CollectServerUsedPorts, collect all of the ports that have been used
func CollectServerUsedPorts(platform string) string {
var (
platformLower = strings.ToLower(platform)
cmd *exec.Cmd
err error
cmdOutPut []byte
) // 策略方法,获取操作系统对应的实例(接口)
if portCollector, ok := used_ports.Find(platformLower); ok {
cmd = portCollector.CollectHaveUsedPorts()
} if cmd != nil {
if cmdOutPut, err = cmd.Output(); err != nil {
log.Errorf("err to execute command %s, %s", cmd.String(), err.Error())
return ""
}
return strings.Trim(string(cmdOutPut), "\n")
} return ""
}
总结
- 程序级别的控制可以在 编译时使用
GOOS=windows来区分编译成对应操作系统的二进制文件 - 文件级别的控制可以在文件头上使用
// + build windows进行控制 - 代码级别的控制,可以是使用 结构体映射接口的方式进行区分
init()初始化方法的使用- 不同结构体只要实现了同一个接口的所有方法,那么可以使用 字典接口来实现代码层面的控制
使用interface化解一场因操作系统不同导致的编译问题的更多相关文章
- [转]9个offer,12家公司,35场面试,从微软到谷歌,应届计算机毕业生的2012求职之路
1,简介 毕业答辩搞定,总算可以闲一段时间,把这段求职经历写出来,也作为之前三个半月的求职的回顾. 首先说说我拿到的offer情况: 微软,3面->终面,搞定 百度,3面->终面,口头of ...
- 9个offer,12家公司,35场面试,从微软到谷歌,应届计算机毕业生的2012求职之路
1,简介 毕业答辩搞定,总算可以闲一段时间,把这段求职经历写出来,也作为之前三个半月的求职的回顾. 首先说说我拿到的offer情况: 微软,3面->终面,搞定 百度,3面->终面,口头of ...
- (转)9个offer,12家公司,35场面试,从微软到谷歌,应届计算机毕业生的2012求职之路
原文:http://www.cnblogs.com/figure9/archive/2013/01/09/2853649.html 1,简介 毕业答辩搞定,总算可以闲一段时间,把这段求职经历写出来,也 ...
- 转:9个offer,12家公司,35场面试 从微软到谷歌,应届计算机毕业生的2012求职之路 !!!
1,简介 毕业答辩搞定,总算可以闲一段时间,把这段求职经历写出来,也作为之前三个半月的求职的回顾. 首先说说我拿到的offer情况: 微软,3面->终面,搞定 百度,3面->终面,口头of ...
- 我的求职之路:9个offer,12家公司,35场面试,最终谷歌【转载】
作者:Luc(写于2012年) 一.简介 毕业答辩搞定,总算可以闲一段时间,把这段求职经历写出来,也作为之前三个半月的求职的回顾. 首先说说我拿到的offer情况: 微软,3面->终面,搞定 百 ...
- 智能驾驶操作系统OS
智能驾驶操作系统OS 自动驾驶操作系统是一个流程化.复杂的综合系统,设计到众多流程和领域.首先,分为不同的层,包括:感知层.认知层.决策规划层.控制层和执行层几个层面. 自动驾驶操作系统是一个流程化. ...
- Device.js – 快速检测平台、操作系统和方向信息
在 Web 项目中,有时候我们需要根据程序运行的环境采取特定操作.Device.js 是一个很小的 JavaScript 库,它简化了编写和平台,操作系统或浏览器相关的条件 CSS 或 JavaScr ...
- Golang 之 interface接口全面理解
什么是interface 在面向对象编程中,可以这么说:“接口定义了对象的行为”, 那么具体的实现行为就取决于对象了. 在Go中,接口是一组方法签名(声明的是一组方法的集合).当一个类型为接口中的所有 ...
- GNU/Linux操作系统总览
计算机科学本科的专业课包括高等数学.离散数学.模拟电子技术.数字电子技术.微机原理.汇编语言原理.高级程序语言.操作系统原理.高级编译原理.嵌入式原理.网络原理.计算机组成与结构等诸多科目.GNU计算 ...
- c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程
c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...
随机推荐
- Docker学习路线10:容器安全
容器安全是实施和管理像Docker这样的容器技术的关键方面.它包括一组实践.工具和技术,旨在保护容器化应用程序及其运行的基础架构.在本节中,我们将讨论一些关键的容器安全考虑因素.最佳实践和建议. 容器 ...
- HMS Core机器学习服务,高效助力跨语种沟通
5月24日, HUAWEI Developer Day(简称HDD)线上沙龙·创新开发专场活动成功举办.HMS Core机器学习服务(ML Kit)产品经理在会上围绕机器翻译的技术优势.使用场景和接入 ...
- IDEA 各个版本下载指引
1.IDEA 其它版本下载指引 网址: https://www.jetbrains.com.cn/idea/download/other.html 2.下载问题 下载哪个版本? win + R 打开命 ...
- 【中秋国庆不断更】XML在HarmonyOS中的生成,解析与转换(下)
一.XML解析 对于以XML作为载体传递的数据,实际使用中需要对相关的节点进行解析,一般包括解析XML标签和标签值.解析XML属性和属性值.解析XML事件类型和元素深度三类场景. XML模块提供Xml ...
- 保护C#代码的艺术:深入浅出代码混淆技术
摘要 在C#开发中,代码的保护是一个不可忽视的问题.本文深入探讨了几种常用的C#代码混淆工具,帮助开发者理解如何有效地保护代码不被反编译.同时,本文也对混淆技术的优缺点进行了分析,并提供了一些实际使用 ...
- CentOS 5.5编译安装vsftpd-2.3.4配置虚拟用户
CentOS 5.5编译安装vsftpd-2.3.4配置虚拟用户 2012-02-22 17:18:15| 分类: rhel_vsftp|举报|字号 订阅 centos 5.5编译安装vsftpd- ...
- 2022飞天技术峰会:硬之城如何基于 SAE 打造数智化电子工业互联网平台
简介: 全球数字化时代已经到来,数字经济正推动生产方式.生活方式和治理方式的深刻变化,成为重组全球要素资源,重塑经济结构,改变全球竞争格局的关键力量. 本文根据硬之城 CEO 李六七先生在峰会上的分享 ...
- Java Agent 踩坑之 appendToSystemClassLoaderSearch 问题
简介: 从 Java Agent 报错开始,到 JVM 原理,到 glibc 线程安全,再到 pthread tls,逐步探究 Java Agent 诡异报错. 作者:鲁严波 从 Java Age ...
- Flink 作为现代数据仓库的统一引擎:Hive 集成生产就绪!
在2020年,你的数据仓库和基础设施需要满足哪些需求? 我们总结了几下几点: 首先,当下的企业正快速转向更实时化的模式,这要求企业具备对线上流式数据进行低延迟处理的能力,以满足实时(real-time ...
- Serverless 场景排查问题利器 : 函数实例命令行操作
简介:实例命令行功能的推出希望能消除用户使用 Serverless 的"最后一公里",直接将真实的函数运行环境展现给用户. 背景介绍 全托管的 Serverless 计算平台能给 ...