作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。

目前几乎所有的应用都会跟网络打交道,所以我们了解和熟悉网络对我们后续的排错是很有必要的,我这里讲解的部分主要是我个人理解来进行讲解。
前面我们介绍 TCP 的 4 次挥手,在被动关闭方里面有一个状态就是 CLOSE_WAIT,今天我们结合实际情况来讲解如果它多了以后会发生什么事情。
但是讲解他之前我们还需要重点介绍一下5元组,因为我们的CLOSE_WAIT状态实际和这个5元组强相关。
源 IP 地址(Source IP):发送方主机地址
源端口(Source Port):发送方应用端口(0~65535)
目的 IP 地址(Destination IP):接收方主机地址
目的端口(Destination Port):接收方服务端口
传输层协议(Protocol):TCP(6)、UDP(17)、ICMP 等
示例:
192.168.1.100:54321 → 123.45.67.89:80 (TCP)从上面我们看到了一个54321端口,这个端口是应用发起方从内核里面随机选的一个,但是这个随机的范围从哪里来的呢?这个也是大部分发行版默认的限制。
[root@localhost ~]# cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999所以当业务很繁忙的时候就会出现问题
端口耗尽问题:对于主动发起连接的客户端,每个连接都占用一个本地端口。TIME_WAIT期间端口无法被复用。在Linux系统中,本地端口的范围通常是32768-61000,约28232个端口。如果每秒建立大量短连接,很快就会耗尽可用端口。
连接建立延迟:某些情况下,即使端口没有完全耗尽,频繁的TIME_WAIT也会导致连接建立变慢。因为内核需要检查端口是否可用,增加了额外的开销。
资源占用:每个TIME_WAIT连接会占用约0.5KB的内核内存,虽然比CLOSE_WAIT少很多,但数十万个TIME_WAIT连接仍然会占用可观的内存资源。
注意:这里的端口耗尽只有源 IP、目标 IP、目标端口都相同时,才会受单个端口的限制。
下面我涉及一个程序来模拟这个问题,这个是在python2.7下模拟成功,服务端和客户端都在本机。
# -*- coding: utf-8 -*-
import socket
import time
import errno
PORT = 8888
# 先启动服务端(非线程,直接启动)
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', PORT))
server.listen(1000)
# 服务端 accept 会阻塞,所以客户端必须放子进程
# 这里用最简单方案:fork 分裂出服务端 + 客户端
import os
pid = os.fork()
if pid == 0:
# 子进程:服务端
while 1:
try:
c, addr = server.accept()
c.close()
except:
break
else:
# 父进程:客户端
time.sleep(1)
sockets = []
count = 0
print "[client] start..."
while True:
try:
sock = socket.socket()
sock.connect(('127.0.0.1', PORT))
count += 1
try:
sock.recv(1024)
except:
pass
sockets.append(sock)
if count % 100 == 0:
print "[client] total: %d , CLOSE_WAIT: %d" % (count, len(sockets))
except Exception, e:
print "[client] stop: %s" % e
print "CLOSE_WAIT num:", len(sockets)
while 1:
time.sleep(1)启动以后会把本地随机端口用光。

然后我们开新创建手工使用curl命令链接也会是一样的效果。

甚至我们使用telnet测试连通性一样的情况
