文章目录
- [go实现定时检查大量的 CLOSE_WAIT 连接](#go实现定时检查大量的 CLOSE_WAIT 连接)
-
- [背景:为什么监控指定端口上的 CLOSE_WAIT 连接数量原因:](#背景:为什么监控指定端口上的 CLOSE_WAIT 连接数量原因:)
- 什么是CLOSE_WAIT
- [go实现定时检查大量的 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
go
package main
import (
"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 options
port := cctx.String("port") // 获取命令行参数中的端口号
// 获取阈值
threshold := cctx.Int("threshold")
var (
cmd *exec.Cmd
output []byte
err 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 := 0
wg.Add(1)
go func(t *time.Ticker) {
defer wg.Done()
for { //循环
<-t.C
i++
fmt.Println("i = ", i)
// 生成Cmd
cmd = 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 integer
closeWaitCount, 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 is
if 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