Python 找出多个连续的空闲端口

项目需求

代码检测某个端口是否被占用,从而找出多个连续空闲的端口,由于需要 Windows、Linux 都能运行,所以选择了 Python,而不是 shell。

实现方案

Python 的 socket 模块

思路是 try 尝试连接某个端口,如果能连接上,表示端口被占用,否则端口空闲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def isInuse(ipList, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
flag=True
for ip in ipList:
try:
s.connect((ip, int(port)))
s.shutdown(2)
print '%d is inuse' % port
flag=True
break
except:
print '%d is free' % port
flag=False
return flag

try 模块中 如果 s.connect((ip, int(port))) 如果能成功, 说明端口被占用.

否则, connect 不成功, 会进到except 中, 说明端口不被占用.

存在问题

1
2
3
4
netstat -tnpl
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 11563/java
tcp 0 0 127.0.0.1:8005 0.0.0.0:* LISTEN 11563/java

源 ip 、源端口 + 目的 ip、目的端口 可以确定一条 tcp 连接。

本来以为 s.connect((ip, int(port))) ,只需要检测 "127.0.0.1","0.0.0.0"

后来发现还有可能是局域网ip 如 10.170.70.87 等等

可以通过这个方法根据远程 hostname 获得其 ip

1
2
3
def getLocalIp():
localIP = socket.gethostbyname(socket.gethostname())
return localIP

代码

从某个端口 port 开始,进行检测,port + 1、port + 2 … ,如果连续的空闲端口数目达到需求的 N,则 port ~ port + N 为需求的端口。

如果中途某个 port + L 端口被占用,那么从新的端口 port + L + 1 开始寻找。

本代码只针对 ipList = ("127.0.0.1","0.0.0.0",getLocalIp()) 这3个 ip 进行 connect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import sys
import os
import socket
def isInuse(ipList, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
flag=True
for ip in ipList:
try:
s.connect((ip, int(port)))
s.shutdown(2)
print '%d is inuse' % port
flag=True
break
except:
print '%d is free' % port
flag=False
return flag
def getLocalIp():
localIP = socket.gethostbyname(socket.gethostname())
return localIP
def checkNinePort(startPort):
flag = True
ipList = ("127.0.0.1","0.0.0.0",getLocalIp())
for i in range(1, 10):
if (isInuse(ipList, startPort)):
flag = False
break
else:
startPort = startPort + 1
return flag, startPort
def findPort(startPort):
while True:
flag, endPort = checkNinePort(startPort)
if (flag == True): #ninePort is ok
break
else:
startPort = endPort + 1
return startPort
def main():
startPort=51988
# startPort = int(sys.argv[1])
print findPort(startPort)
main()

字符串匹配

第一种方法的准确性依赖于 connect((ip, int(port))) 中的 ip,

需要找到完备的 ip 集合的, 才可以确定这个端口不被占用,难度较大

思路

**在 linux 用 netstat -tnpl 可以得到端口监听信息,

观察 tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 11563/java

出现了 0 0.0.0.0:8080 所以 8080端口是被占用的


对这些输出的字符信息进行搜索 :8080, 如果存在, 就表示 8080 端口是LISTEN.

如果输出结果中不存在 :8080 的相关字符,表示这个端口不被占用.

代码

WINDOWS、 LINUX 这几个平台查看端口信息的方式不同,

先进行机器平台的判断,然后调用各个平台的端口占用判断函数

因为需求是找出连续的空闲端口,当中途只要有一个端口占用, 就跳出循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import os
import platform
import sys
def isInuseWindow(port):
if os.popen('netstat -an | findstr :' + str(port)).readlines():
portIsUse = True
print '%d is inuse' % port
else:
portIsUse = False
print '%d is free' % port
return portIsUse
def isInuseLinux(port):
#lsof -i:8080
#not show pid to avoid complex
if os.popen('netstat -na | grep :' + str(port)).readlines():
portIsUse = True
print '%d is inuse' % port
else:
portIsUse = False
print '%d is free' % port
return portIsUse
def choosePlatform():
#'Windows-7-6.1.7601-SP1'
#'linux-2.6.32-431.23.3.el6.x86_64-x86_64-with-centos-6.5-final'
machine = platform.platform().lower()
if 'windows-' in machine:
return isInuseWindow
elif 'linux-' in machine:
return isInuseLinux
else:
print 'Error, sorry, platform is unknown'
exit(-1)
def checkNinePort(startPort):
isInuseFun = choosePlatform()
nineIsFree = True
for i in range(1, 10):
if (isInuseFun(startPort)):
nineIsFree = False
break
else:
startPort = startPort + 1
return nineIsFree, startPort
def findPort(startPort):
while True:
flag, endPort = checkNinePort(startPort)
if (flag == True): # ninePort is ok
break
else:
startPort = endPort + 1
return startPort
def main(startPort):
firstPort=findPort(startPort)
print 'First port of nine free ports is ', firstPort
if __name__ == '__main__' :
if len(sys.argv) > 1:
print len(sys.argv)
startPort = int(sys.argv[1])
else:
startPort = 500
main(startPort)

相关知识点总结

1
os.popen()

可以调用系统的一些shell命令

1
os.popen().readlines()

读取调用shell命令后的回显信息

1
2
3
4
5
6
7
8
9
10
11
netstat -tnpl
-tnpl 各个参数的含义
-l或--listening 显示监控中的服务器的Socket。
-n或--numeric 直接使用IP地址,而不通过域名服务器。
-p或--programs 显示正在使用Socket的程序识别码和程序名称。
-t或--tcp 显示TCP传输协议的连线状况
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 9890/nginx
最后的9890/nginx 表示 进程号 9890 进程名 nginx