文章目录
- go实现定时检查大量的 CLOSE_WAIT 连接
- 背景:为什么监控指定端口上的 CLOSE_WAIT 连接数量原因:
- 什么是CLOSE_WAIT
- go实现定时检查大量的 CLOSE_WAIT 连接
- 参考
go实现定时检查大量的 CLOSE_WAIT 连接
监控指定端口的连接状态,特别是关注 CLOSE_WAIT 连接的数量。CLOSE_WAIT 是指 TCP 连接关闭时,连接的一端等待关闭的另一端发送最后的确认信号。如果存在大量的 CLOSE_WAIT 连接,可能意味着网络连接没有正常关闭,可能会导致资源泄漏或其他问题。
背景:为什么监控指定端口上的 CLOSE_WAIT 连接数量原因:
- 资源泄漏检测:大量的 CLOSE_WAIT 连接可能是由于网络连接没有正常关闭导致的资源泄漏。通过监控 CLOSE_WAIT 连接数量,可以及时发现这些连接,从而识别和解决资源泄漏问题。
- 网络连接管理:CLOSE_WAIT 连接可能会占用系统资源,如文件描述符等。通过监控连接数量,可以更好地管理和优化网络连接,确保连接的正常关闭和释放。
- 故障排查:CLOSE_WAIT 连接可能是网络故障或应用程序错误的指示。通过监控连接数量,可以定位和解决潜在的网络问题,加快故障排查的速度。
- 安全性:异常的 CLOSE_WAIT 连接可能是一种恶意行为的指示,如拒绝服务攻击等。通过监控连接数量,可以及时发现可疑连接,采取相应的安全措施。
什么是CLOSE_WAIT
客户端主动关闭连接,服务器接收到客户端的FIN,但是还没有发送自己的FIN,此时的状态为close_wait状态,大量的close_wait状态拖累服务器性能。
主动关闭的一方发出 FIN 包,被动关闭的一方响应 ACK 包,此时,被动关闭的一方就进入了 CLOSE_WAIT 状态。 如果一切正常,稍后被动关闭的一方也会发出 FIN 包,然后迁移到 LAST_ACK 状态。
通常,CLOSE_WAIT 状态在服务器停留时间很短**,如果你发现大量的 CLOSE_WAIT 状态,那么就意味着被动关闭的一方没有及时发出 FIN 包**,一般有如下几种可能:
- 程序问题:如果代码层面忘记了 close 相应的 socket 连接,那么自然不会发出 FIN 包,从而导致 CLOSE_WAIT 累积;或者代码不严谨,出现死循环之类的问题,导致即便后面写了 close 也永远执行不到。
- 响应太慢或者超时设置过小:如果连接双方不和谐,一方不耐烦直接 timeout,另一方却还在忙于耗时逻辑,就会导致 close 被延后。响应太慢是首要问题,不过换个角度看,也可能是 timeout 设置过小。
go实现定时检查大量的 CLOSE_WAIT 连接
通过定期执行 netstat 命令并记录结果,该程序可以提供一种简单的方式来监控 CLOSE_WAIT 连接的数量,并将结果写入日志文件进行进一步分析和处理。
代码位置:https://gitcode.net/inthat/mymonitor
main.go
package mainimport ("context""fmt"lcli "mymonitor/cli"socketmonitorlog "mymonitor/lib/socketmonitorlog""os/exec""os/signal""strconv""strings""sync""syscall""time""io""os"logging "github.com/ipfs/go-log/v2""github.com/urfave/cli/v2"
)var log = logging.Logger("socket-go-monitor")func init() {}func exitHandle(exitChan chan os.Signal) {for {select {case sig := <-exitChan:fmt.Println("接受到来自系统的信号:", sig)os.Exit(1) //如果ctrl+c 关不掉程序,使用os.Exit强行关掉}}}func main() {socketmonitorlog.SetupLogLevels()exitChan := make(chan os.Signal)signal.Notify(exitChan, os.Interrupt, os.Kill, syscall.SIGTERM)go exitHandle(exitChan)app := &cli.App{Name: "socket-go-monitor",Usage: "Start socket monitor",Flags: []cli.Flag{&cli.StringFlag{Name: "port",Aliases: []string{"p"},Usage: "specify monitor port ",},&cli.StringFlag{Name: "threshold",Aliases: []string{"t"},Usage: "specify socket threshold num ",},&cli.BoolFlag{Name: "cmd",Aliases: []string{"s"},Value: false,Usage: "do cmd",},},Action: func(cctx *cli.Context) error {log.Info("Starting socket monitor")ctx := lcli.ReqContext(cctx)ctx, cancel := context.WithCancel(ctx)defer cancel()//get optionsport := cctx.String("port") // 获取命令行参数中的端口号// 获取阈值threshold := cctx.Int("threshold")var (cmd *exec.Cmdoutput []byteerr error)filename := "socket_monitor.txt"file, err := os.Create(filename)if err != nil {fmt.Println(err)}defer file.Close()var wg sync.WaitGroup//创建定时器,每隔600秒后,定时器就会给channel发送一个事件(当前时间)ticker := time.NewTicker(time.Second * 600)defer ticker.Stop()i := 0wg.Add(1)go func(t *time.Ticker) {defer wg.Done()for { //循环<-t.Ci++fmt.Println("i = ", i)// 生成Cmdcmd = exec.Command("/bin/bash", "-c", fmt.Sprintf("netstat -an|grep %s|grep CLOSE_WAIT|wc -l\n", port))// 执行了命令, 捕获了子进程的输出( pipe )if output, err = cmd.CombinedOutput(); err != nil {fmt.Println(err)return}//打印子进程的输出fmt.Println(string(output))// Parse the output as an integercloseWaitCount, err := strconv.Atoi(strings.TrimSpace(string(output)))if err != nil {fmt.Println(err)return}// Check if the count is greater than the threshold and print if it isif closeWaitCount > threshold {var nowtime = time.Unix(time.Now().Unix(), 0).Format("2006-01-02 15:04:05")str := fmt.Sprintf("%s CLOSE_WAIT COUNT: %d \n", nowtime, closeWaitCount)fmt.Println(str)n, err := io.WriteString(file, str)if err != nil {fmt.Println(n, err)}}// if i == 10000000 {// t.Stop() //停止定时器// return// }}}(ticker)wg.Wait()return nil},}app.Setup()//os.Args启动程序if err := app.Run(os.Args); err != nil {log.Warnf("%+v", err)return}fmt.Println("ends")
}
参考
浅谈CLOSE_WAIT
参考URL: https://cloud.tencent.com/developer/article/1918110