快盘下载:好资源、好软件、快快下载吧!

快盘排行|快盘最新

当前位置:首页软件教程电脑软件教程 → 记time_wait状态引起的端口占用排查

记time_wait状态引起的端口占用排查

时间:2022-10-17 21:16:11人气:作者:快盘下载我要评论

文章目录

      • 0. 问题背景
      • 1. 问题定位
        • time_wait状态
        • 确认原因
      • 2. 解决过程
        • 长连接探测
        • 预留端口
        • SO_REUSEADDR和SO_REUSEPORT


0. 问题背景

在Liunx服务器上发现有 10倍于 LISTEN 服务的 time_wait 状态;服务并非高并发;日常的连接数也比较少;因此该现象明显异常
记time_wait状态引起的端口占用排查

1. 问题定位

time_wait状态

回顾下 time_wait 状态处于 TCP 通信的哪个阶段;

在TCP四次挥手过程中;主动断开连接的一方会在发送完最后一个 ACK 包后;等待 2MSL;Maximum Segment Lifetime;的时间;这个阶段就处于 time_wait 状态
time_wait 状态是为了确保;当被动断开连接的一方没有收到最后一个 ACK 包时;会再次发送 FIN 包;如果此时已经建立了新连接;可能被该 FIN 包影响从而导致连接终止

一般在高并发、短连接;单个连接时长超过time_wait时间;的服务端容易出现大量time_wait并存的情况;但在此服务器应不存在

确认原因

首先查看服务器 2MSL 的设置;是正常范围
time_wait
同时发现在 LISTEN 端口上同时存在多个处于 time_wait 状态的本机端口;此时确认应该是另一个本机的扫描程序导致的

time_wait

2. 解决过程

长连接探测

因为此情况下;TCP 连接的两方都在同一台机器上;无法规避 time_wait 状态的存在;因此首先将探测程序改为长连接

这是之前的探测连接代码

// TCP连接端口
func Ping(host string, timeout int) bool {
	_timeout := time.Duration(timeout) * time.Millisecond
	if conn, err := net.DialTimeout(;tcp;, host, _timeout); err != nil {
		return false
	} else {
		conn.Close()
		return true
	}
}

修改后选择 HTTP 长连接的方式;这样可以最大程度规避 time_wait 状态
唯一需要注意的是;HTTP 长连接如果想要复用上一次的连接;哪怕不需要读取数据;也需要调用 ioutil.ReadAll(resp.Body)清空buffer里的数据;否则该连接不会被复用

p := net.TCPAddr{IP: net.ParseIP(addr), Port: port}
// 通过Transport设置最大连接、timeout、
client = &http.Client{
	Transport: &http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   timeout,                                      // transport timeout
			KeepAlive: time.Duration(IdleConnTimeout) * time.Second,
			LocalAddr: &p,
		}).DialContext,
		IdleConnTimeout:       time.Duration(IdleConnTimeout) * time.Second,
		ResponseHeaderTimeout: timeout,
	},
	Timeout: timeout,
}
if req, err := http.NewRequest(;POST;, addr, nil); err == nil {
	// 创建请求
	q := req.URL.Query()
	req.URL.RawQuery = q.Encode()
	// 进行请求
	if resp, err := client.Do(req); err == nil {
		_, e := ioutil.ReadAll(resp.Body) // 必须读取response后才能复用连接
		if err = resp.Body.Close(); err != nil {
			log.Info(;resp body close err: ;, err, ; ;, e)
		}
	}
}

;PS;这里直接用 TCP 连接也可达到同样效果;

而且上述探测功能会固定占用和 LISTEN 端口一样数量的端口;如果和动态分配范围内的端口重合会存在问题
查看机器上动态分配端口的范围;一般为32768-61000
time_wait
所以额外在 Transport 里指定了 LocalAddr;这一步可以绑定固定的端口;将探测端口绑定到61000以上;可以避免端口冲突的问题

time_wait

预留端口

如果 time_wait 状态过多影响剩余端口的分配;可以设置预留端口;来保证time_wait状态不会影响其他功能的使用
Linux 的 net.ipv4.ip_local_port_range参数可以规划出一段端口段预留作为服务端口;可以将服务监听的端口以逗号分隔全部添加到ip_local_reserved_ports中;或直接设置一个端口范围段

这样当 Linux 调用 bind(0) 或者 connect 从ip_local_port_range;前面说的32768-61000;中随机选取源端口时;会排除ip_local_reserved_ports中定义的端口;因此就不会出现端口被占用了服务无法启动

vim /etc/sysctl.conf

# 加入下面这行
# net.ipv4.ip_local_reserved_ports=42310,51000-52000

sysctl -p

SO_REUSEADDR和SO_REUSEPORT

关于这两个参数的概念理解并不是本篇的重点;大家可以参考SO_REUSEADDR和SO_REUSEPORT作用这篇博文的解释

对于time_wait状态较多;但又无法解决的情况下;比如就是需要服务端主动断开连接or服务端还需要请求下游;;可以通过设置 SO_REUSEADDR和SO_REUSEPORT 参数;让 time_wait 状态不要影响正常的服务

可以通过以下方式来进行设置;
;Golang版本可以用syscall来调用系统方法设置;其他语言也有类似方法可以设置;

		import (
			;syscall;
			;golang.org/x/sys/unix;
		)

		if fd, err := syscall.Socket(syscall.AF_INET, syscall.O_NONBLOCK|syscall.SOCK_STREAM, 0); err == nil && port > 0 {
			syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEADDR, 1) // 设置复用端口
			syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
			addr := syscall.SockaddrInet4{Port: port}
			copy(addr.Addr[:], net.ParseIP(;0.0.0.0;).To4())
			syscall.Bind(fd, &addr)
		}

以上是本篇文章的全部内容;下一篇会总结当服务端口频繁被其他随机分配端口占用的情况下;可以如何通过 Golang或其他代码来解决

相关文章

网友评论

快盘下载暂未开通留言功能。

关于我们| 广告联络| 联系我们| 网站帮助| 免责声明| 软件发布

Copyright 2019-2029 【快快下载吧】 版权所有 快快下载吧 | 豫ICP备10006759号公安备案:41010502004165

声明: 快快下载吧上的所有软件和资料来源于互联网,仅供学习和研究使用,请测试后自行销毁,如有侵犯你版权的,请来信指出,本站将立即改正。