#本文仅供参考有不足之处请指出

一、设计环境

系统:Windows 11

语言:python3.8

编译器:PyCharm

二、设计要求

1、局域网内的存活主机发现

2、扫描指定主机开放的端口

3、图形化界面、多线程

三、基本原理

1、主机发现是利用系统ping命令ping整个网段主机。

2、端口扫描通过与目标端口进行socket连接,如果能连接的上则端口为开放,反之就是关闭。

四、设计代码

from tkinter import *
import socket, re
import threading
import time
import queue
import tkinter.messagebox as msgbox
import platform
import os
import sys

class MyGui():
    def __init__(self, init_window_name):
        self.init_window_name = init_window_name

    def set_init_window(self):
        self.k = 0
        # 初始化窗口
        self.init_window_name.title("IP端口扫描器")
        self.window_center(420, 520)
        self.init_window_name.resizable(0, 0)  # 固定窗口,禁止放大缩小

        # 定义区域,把全局分为上中下部分
        self.frame_top = Frame(self.init_window_name, width=420, height=200)
        self.frame_center = Frame(self.init_window_name, width=420, height=300)
        self.frame_foot = Frame(self.init_window_name, width=420, height=10)
        self.frame_top.grid(row=0, column=0)
        self.frame_center.grid(row=1, column=0)
        self.frame_foot.grid(row=2, column=0, pady="20")

        # 上部分布局
        self.ipscan_lb_top = Label(self.frame_top, text="IP网段", font="宋体")
        self.ip_lb_top = Label(self.frame_top, text="IP地址", font="宋体")
        self.port_lb_top = Label(self.frame_top, text="端口范围", font="宋体")
        self.ipscan_input = StringVar()
        self.ip_input = StringVar()
        self.port_input = StringVar()
        self.ipscan_input.set('格式:'+'.'.join(self.my_ip().split('.')[:-1]) + '.'+'1')
        self.ip_input.set('格式:'+'127.0.0.1')
        self.port_input.set('1-65535')#1-65535

        #定义事件
        self.ipscan_content = Entry(self.frame_top, textvariable=self.ipscan_input, font="宋体",)
        self.ip_content = Entry(self.frame_top, textvariable=self.ip_input, font="宋体")
        self.port_content = Entry(self.frame_top, textvariable=self.port_input, font="宋体")
        self.IPscan = Button(self.frame_top, text="IP扫描",command=self.ping_all ,font="宋体")#command=lambda:[funcA(), funcB(), funcC()]
        self.IPStopScan = Button(self.frame_top, text="IPStop", command=self.stop, font="宋体", fg='red')
        self.PortScan = Button(self.frame_top, text="端口扫描", command=self.runing, font="宋体")
        self.StopScan = Button(self.frame_top, text="停止扫描", command=self.stop, font="宋体", fg='red')

        #每个具体位置
        self.ipscan_lb_top.place(x=30, y=20)
        self.ipscan_content.place(x=115, y=20)
        self.ip_lb_top.place(x=30, y=60)
        self.ip_content.place(x=115, y=60)
        self.port_lb_top.place(x=30, y=100)
        self.port_content.place(x=115, y=100)
        self.IPscan.place(x=320,y=20)
        self.IPStopScan.place(x=320, y=60)
        self.PortScan.place(x=60, y=140)
        self.StopScan.place(x=270, y=140)

        # 中间部分布局
        self.porttext = Text(self.frame_center, width=42, height=18, font="宋体", fg='blue')
        self.scroll = Scrollbar(self.frame_center)
        self.scroll.pack(side=RIGHT, fill=Y)
        self.porttext.pack(side=RIGHT, fill=Y)
        self.scroll.config(command=self.porttext.yview)
        self.porttext.config(yscrollcommand=self.scroll.set)

        # 下部分布局

    '''
    Python局域网扫描获取存活主机IP
    '''

    # 获取本机操作系统名称
    def my_os(self):
        return platform.system()

    def my_ip(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
        s.close()
        return ip

    #ping所有IP获取所有存活主机
    def ping_all(self):
        self.k = 2
        self.porttext.delete(1.0, END)
        if re.match(r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
                    self.ipscan_content.get()):
            self.ip = self.ipscan_content.get()
        else:
            msgbox.showerror(title="Error", message="ip格式错误或不合法,请重新输入")
            return
        self.ip = self.ipscan_content.get()
        pre_ip = (self.ip.split('.')[:-1])
        self.porttext.insert(END,'扫描的IP网段为:' + '.'.join(pre_ip) + '.' + '1/24\n' + '\n扫描结果\n\n')
        time_start = time.time()  # 开始计时
        #self.porttext.insert(END, '开始时间为:' + time_start + '\n')

        ip_queue = queue.Queue()
        for i in range(1, 256):
            add = ('.'.join(pre_ip) + '.' + str(i))
            ip_queue.put(add)
            self.MyThread(self.ping_ip, ip_queue)
            #threading._start_new_thread(self.ping_ip, (add,))
        # time_end = time.time()  # 结束计时
        # time_all = time_end - time_start  # 运行所花时间
        # avg_time = time_all / 255
        #self.porttext.insert(END, '总耗时:' + str("%.2f" % time_all) + 's   ' + '平均耗时:' + str("%.2f" % avg_time) + 's' '\n\n')

    # ping指定IP判断主机是否存活
    def ping_ip(self,ip_queue):
        while self.k == 2:
            if ip_queue.empty():
                break
            ip = ip_queue.get()
            if self.my_os() == 'Windows':
                p_w = 'n'
            elif self.my_os() == 'Linux':
                p_w = 'c'
            else:
                self.porttext.insert(END,'不支持此操作系统')
                sys.exit()
            output = os.popen('ping -n 1 -%s 1 %s' % (p_w, ip)).readlines()
            # print (output[1])
            for w in output:
                if str(w).upper().find('TTL') >= 0:
                    outputname = os.popen('ping -a -n 1 -%s 1 %s' % (p_w, ip)).readlines()
                    hostnameip = str(outputname[1].split("具有 32 字节的数据")[0].split("正在 Ping")[1])
                    self.porttext.insert(END,"IP:" + hostnameip + "is OK\n")
                    self.porttext.see(END)
    '''
    Python扫描主机IP开放的端口
    '''
    # 窗口居中
    def window_center(self, width, height):
        screenwidth = self.init_window_name.winfo_screenwidth()
        screenheight = self.init_window_name.winfo_screenheight()
        size = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
        self.init_window_name.geometry(size)

    # 获取当前时间
    def get_current_time(self):
        current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
        return current_time

    #多线程
    class MyThread(threading.Thread):
        def __init__(self, func, *args):
            super().__init__()

            self.func = func
            self.args = args

            self.setDaemon(True)
            self.start()  # 在这里开始
            self.threadLock = True

        def run(self):
            self.func(*self.args)

    #停止扫描函数
    def stop(self):

        if self.k == 0:
            msgbox.showinfo(title='提示', message='扫描未开始 或 已停止')

        else:
            self.k = 0
            self.porttext.insert(END, '\n' + '扫描已暂停......')

    #端口扫描开始函数
    def runing(self):
        self.k = 1
        self.porttext.delete(1.0, END)  #清空text框

        if re.match(r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
                    self.ip_content.get()):
            self.ip = self.ip_content.get()
        else:
            msgbox.showerror(title="Error", message="ip格式错误或不合法,请重新输入")
            return

        if re.match(r"^(?:[0-9]{1,5}-){1}[0-9]{1,5}$", self.port_content.get()):
            ports = self.port_content.get()
        else:
            msgbox.showerror(title="Error", message="端口范围·格式错误,格式为:xxx-xxx")
            return

        ports = ports.split('-')
        startport = ports[0]
        endport = ports[1]
        p = 0
        cur_time = self.get_current_time()
        self.porttext.insert(END,
                             "当前时间为:" + cur_time + "\nIP地址:" + self.ip + "\n端口范围:" + self.port_content.get() + "\n\n扫描结果\n\n")
        self.portslist = list(range(int(startport), int(endport) + 1))
        port_queue = queue.Queue()
        for port in self.portslist:
            port_queue.put(port)
            p += 1

        for i in range(0, 3000):
            self.MyThread(self.scan, port_queue)
        if p == int(endport):
            end_time = self.get_current_time()
            #self.porttext.insert(END, '结束时间为:' + end_time + '\n')

    def scan(self, port_queue):
        while self.k == 1:
            if port_queue.empty():
                break

            port = port_queue.get()
            timeout = 3
            s = socket.socket()
            s.settimeout(timeout)

            try:
                Type_Dict = {
                    21:'服务:FTP;\n说明:FTP服务器所开放的端口,用于上传、下载。',
                    22: "服务:SSH;\n说明:PcAnywhere建立的TCP和这一端口的连接可能是为了寻找ssh。",
                    25: "服务:SMTP;\n说明:SMTP服务器所开放的端口,用于发送邮件。",
                    80: "服务:HTTP;\n说明:用于网页浏览。木马Executor开放此端口。",
                    109: "服务:POP3;\n说明:POP3服务器开放此端口,用于接收邮件,客户端访问服务器端的邮件服务。",
                    135: "服务:Location Service;\n说明:Microsoft在这个端口运行DCE RPC end - point mapper为它的DCOM服务。",
                    139: "服务:NETBIOS Name Service;\n说明:通过这个端口进入的连接试图获得NetBIOS/SMB服务。这个协议被用于windows文件和打印机共享和SAMBA。",
                    443: "服务:HTTPS;\n说明:网页浏览端口,能提供加密和通过安全端口传输的另一种HTTP。"
                }
                s.connect((self.ip, port))
                for k, v in Type_Dict.items():
                    if port == k:
                        string = "Port " + str(port) + " is OPEN!\n"+Type_Dict[port]+'\n'
                        break
                    else:
                        string = "Port " + str(port) + " is OPEN!\n"
                self.porttext.insert(END, string)
                self.porttext.see(END)

            except Exception as e:
                pass
            finally:
                s.close()

if __name__ == "__main__":
    pygui = Tk()
    init_window = MyGui(pygui)
    init_window.set_init_window()
    pygui.mainloop()

五、运行界面

1、IP扫描

python局域网扫描 python 局域网主机扫描_IP

2、端口扫描

python局域网扫描 python 局域网主机扫描_python_02

六、总结

 ip扫描部分,停止扫描功能不完善,还需改进

端口扫描不存在的主机也会显示个别端口,原因不知,未做过多了解

还有就是对于多线程的开始时间和结束时间的判断和处理还存在问题所以程序结果输出未显示时间