传送门
本系列原创博文传送门:
正文
这一篇主要实现端口的自动遍历。
根据端口的百科资料,一个ip的可用端口数是 2^16 = 65536 。
那么遍历一个ip的端口,就需要调用那么多次端口访问。因为经常要调用端口访问的代码块,我们先把这部分代码提取成一个函数。
# coding=utf-8
import socket
def port_search(ip, port):
address = (ip, port) # 地址必须是一个元祖,第一个是str,第二个是int
client = socket.socket() # 创建套接字
err = client.connect_ex(address)
if err == 0: # 当端口开启时,错误码是0,其他错误码均表示未开该端口
print(client.recv(1024))
client.close() # 关闭连接
print(err)
port_search(ip='110.242.68.4', port=22)
考虑到端口数量众多,我们看一下运行一次的时间,引入time模块:
# coding=utf-8
import socket
import time
def port_search(ip, port):
address = (ip, port) # 地址必须是一个元祖,第一个是str,第二个是int
client = socket.socket() # 创建套接字
err = client.connect_ex(address)
if err == 0: # 当端口开启时,错误码是0,其他错误码均表示未开该端口
print(client.recv(1024))
client.close() # 关闭连接
print(err)
start_time = time.time()
port_search(ip='110.242.68.4', port=22)
print('消耗时间为: ', time.time()-start_time)
试了下,不开放的端口耗时:
将port值修改为80:
发现每一个端口都耗时很久,假设按20秒计算,遍历完所有端口耗时=65536*20秒……好像有点久!
为了加快遍历速度,我们引入线程threading模块(教程见此),线程的意义就是并发,而非串行。
第一阶段,加入端口遍历后,对于port_search函数通过实现线程的创建和运行来调用。
# coding=utf-8
import socket
import threading
def port_search(_ip, _port):
address = (_ip, _port) # 地址必须是一个元祖,第一个是str,第二个是int
client = socket.socket() # 创建套接字
err = client.connect_ex(address)
if err == 0: # 当端口开启时,错误码是0,其他错误码均表示未开该端口
print(client.recv(1024))
client.close() # 关闭连接
print(err)
ip = '110.242.68.4'
for i in range(65536): # 0~65535
thread = threading.Thread(target=port_search, args=(ip, i))
thread.start()
这个程序还有问题,若是运行完,有很多个输出,输出很杂乱,需要将信息归整,修改之后如下:
import socket
import threading
global_list = [] # 收集开着的端口信息
global_err_list = [] # 收集关着的端口信息
def port_search(_ip, _port):
address = (_ip, _port) # 地址必须是一个元祖,第一个是str,第二个是int
client = socket.socket() # 创建套接字
code = client.connect_ex(address)
if code == 0: # 当端口开启时,错误码是0,其他错误码均表示未开该端口
global_list.append([_ip, _port, code, client.recv(1024)])
else:
global_err_list.append([_ip, _port, code]) # 此处不能调用 recv api,会抛出异常
client.close() # 关闭连接
ip = '110.242.68.4'
for i in range(65536): # 0~65535
thread = threading.Thread(target=port_search, args=(ip, i))
thread.start()
print(global_list)
print(global_err_list)
到这里之后,还有两个潜在的问题:
1、这两个全局列表被并发访问,需要加一个锁;
2、打印的时候,有些线程没有结束,所以会错过一些信息,需要使用join阻塞来等待所有线程结束。
再次修改如下:
# coding=utf-8
import socket
import threading
global_list = [] # 收集开着的端口信息
global_err_list = [] # 收集关着的端口信息
global_thread_list = [] # 线程收集列表
lock = threading.Lock() # 创建一个锁
def port_search(_ip, _port):
address = (_ip, _port) # 地址必须是一个元祖,第一个是str,第二个是int
client = socket.socket() # 创建套接字
code = client.connect_ex(address)
lock.acquire()
if code == 0: # 当端口开启时,错误码是0,其他错误码均表示未开该端口
global_list.append([_ip, _port, code, client.recv(1024)])
else:
global_err_list.append([_ip, _port, code]) # 此处不能调用 recv api,会抛出异常
lock.release()
client.close() # 关闭连接
ip = '110.242.68.4'
for i in range(65536): # 0~65535
thread = threading.Thread(target=port_search, args=(ip, i))
global_thread_list.append(thread)
thread.start()
for t in global_thread_list: # 遍历,等待所有进程运行完毕
t.join()
print(global_list)
print(global_err_list)
走读代码时发现有个风险点,如果在加锁和解锁之前的代码段抛出异常,一个线程崩溃,会导致锁无法解开,修改如下:
# coding=utf-8
import socket
import threading
global_list = [] # 收集开着的端口信息
global_err_list = [] # 收集关着的端口信息
global_thread_list = [] # 线程收集列表
lock = threading.Lock() # 创建一个锁
def port_search(_ip, _port):
address = (_ip, _port) # 地址必须是一个元祖,第一个是str,第二个是int
client = socket.socket() # 创建套接字
code = client.connect_ex(address)
lock.acquire()
try:
if code == 0: # 当端口开启时,错误码是0,其他错误码均表示未开该端口
global_list.append([_ip, _port, code, client.recv(1024)])
else:
global_err_list.append([_ip, _port, code]) # 此处不能调用 recv api,会抛出异常
except BaseException:
print('进行列表操作时报错')
lock.release()
client.close() # 关闭连接
ip = '110.242.68.4'
for i in range(65536): # 0~65535
thread = threading.Thread(target=port_search, args=(ip, i))
global_thread_list.append(thread)
thread.start()
for t in global_thread_list: # 遍历,等待所有进程运行完毕
t.join()
print(global_list)
print(global_err_list)
初步试运行了一下,卡的不行。加一个最大线程限制:
# coding=utf-8
import socket
import threading
global_list = [] # 收集开着的端口信息
global_err_list = [] # 收集关着的端口信息
global_thread_list = [] # 线程收集列表
lock = threading.Lock() # 创建一个锁
sem = threading.Semaphore(500) # 并发500
def port_search(_ip, _port):
address = (_ip, _port) # 地址必须是一个元祖,第一个是str,第二个是int
client = socket.socket() # 创建套接字
code = client.connect_ex(address)
lock.acquire() # 锁定
try:
if code == 0: # 当端口开启时,错误码是0,其他错误码均表示未开该端口
global_list.append([_ip, _port, code, client.recv(1024)])
else:
global_err_list.append([_ip, _port, code]) # 此处不能调用 recv api,会抛出异常
except BaseException:
print('进行列表操作时报错')
lock.release() # 释放锁
client.close() # 关闭连接
ip = '110.242.68.4'
for i in range(65536): # 0~65535
with sem: # 并发限制
thread = threading.Thread(target=port_search, args=(ip, i))
global_thread_list.append(thread)
thread.start()
for t in global_thread_list: # 遍历,等待所有进程运行完毕
t.join()
print(global_list)
# print(global_err_list)
遍历下来后,输出结果为:
[['110.242.68.4', 80, 0, b''], ['110.242.68.4', 443, 0, b'']]
就是web服务http和https两大协议使用的端口。
好了,这个核心逻辑应该就没啥问题了。
但是这种用户界面就太LOW了吧?所以后面我们将使用PySide2来制作工具界面。
下一篇将开始接触PySide2。