一、前言
1.实验目的
参照superscan等端口扫描软件的运行情况,自行设计一个简单的端口扫描软件。能够根据TCP、IMCP等探测方法,设计程序对IP地址(单个IP,一段IP范围)、指定主机名的端口(部分端口,所有端口)进行扫描,以获得相关的信息。要求多线程技术实现;扫描过程中能显示扫描时间;图形化界面;异常告警窗口(如IP地址范围出界等)。
2.实验环境
电脑需要安装java虚拟机(JDK7或者JDK8),使用eclipse软件运行java程序,实现端口扫描。
3.安装说明
安装好JDK8以后,打开eclipse,新建一个package“a_scan_own”,新建一个类“OwnTCPScan”,将代码拷贝进去,并将passwd.txt文件放入“a_scan_own”内。
请注意,该代码内包含密码文件读写操作,因此需要修改OwnTCPScan.java文件的500行与1043行的代码,将“E:\grade_3\grade_3_code\first_semester\JavaGrade3\src\”改为该package所在的位置,即可运行。
4.操作系统版本
Windows8、Windows10的32位、64位均可以运行本代码。
二、系统分析
2.1 基本要求
本课题的基本要求如下:
- 该端口扫描程序可以对IP地址进行扫描,可以扫描单个IP地址和一段范围内的IP地址;
- 该端口扫描程序可以对指定主机名的端口进行扫描,可以扫描部分端口或者所有端口;
- 采用多线程技术,可以由用户控制同时运行的线程数;
- 可以显示扫描时间和异常警告窗口(当用户输入不合法的信息时,给予提示);
- 实现图形化界面,界面友好、美观、可用性强。
2.2 实验内容
本课题实现了所有的基本要求,并附加了一部分功能,以提高用户的使用舒适度。用户使用该端口扫描设计软件的过程如下:
用户输入用户名与密码进入主界面;
用户可在主界面进行扫描操作和文件操作;
扫描操作下,用户需要选择扫描方式,包括“根据IP地址扫描”和“根据主机名与端口号扫描”。其中,“根据IP地址扫描”的选项下包括“单个IP地址”和“一段IP地址”;“根据主机名与端口号扫描”的选项下包括“常用端口”和“部分端口”。用户可以点击“查看”按钮查看常用端口,也可以选择“部分端口”,输入端口范围进行扫描。
扫描方式选择完成后,用户需要输入线程数。之后,点击“开始扫描”,扫描结果与扫描时间会自动显示在扫描结果显示框内。若点击“退出”,系统会弹出“确认退出吗?”的对话框以提醒用户。若点击“清空”,则会自动清空界面[1]。
文件操作下,用户可以选择“修改密码”、“保存扫描结果”和“退出”。若点击“修改密码”,则会提示用户修改登录密码,保存密码的文件在该package目录下,打开之后,可以见到输入密码的密文形式。若点击“保存扫描结果”,用户可以自行选择将扫描结果保存在哪个目录下。若点击“退出”,系统会弹出“确认退出吗?”的对话框以提醒用户。
三、代码关键点
IP地址的多线程扫描
IP地址扫描采用多线程技术,以用户输入的IP地址和线程数为参数采用多线程的方式扫描IP地址,调用PingTester类实现IP地址扫描,PingTester类包含3个部分:构造函数、startPing函数、内部类PingRunner。
1.构造函数
构造函数PingTester(int fromip1,int fromip2,int fromip3,int fromip4,int toip3,int toip4),用于传递用户输入的参数起始IP地址,并生成包含所有的IP地址的LinkedList,对象名为allIP。具体代码出现在OwnTCPScan.java文件的905~921行。
2.startPing函数
startPing函数用于创建一个线程池[5],使用ExecutorService类的对象进行多线程异步执行。startPing方法中声明了ExecutorService类的executor对象,并调用了该对象的executor方法,以异步的方式调用PingRunner类,执行多线程。具体代码出现在OwnTCPScan.java文件的922~943行。
3.PingRunner类
内部类PingRunner用于扫描IP地址并输出IP地址的可达性信息。PingRunner以Runnable类为接口,实现抽象方法run(),调用InetAddress类的isReachable()函数,判断该IP地址是否可达,若可达,则将结果添加到最终的扫描结果;若不可达,则不做任何操作。具体代码出现在OwnTCPScan.java文件的945~981行。
端口号的多线程扫描
根据主机和端口号的扫描采用多线程技术,以用户输入的主机名、线程数与端口范围为参数进行多线程扫描,调用scanPorts函数传递参数,scanPorts函数调用ScanMethod类实现端口的扫描。主要分为3个部分:scanPorts函数、ScanMethod类的构造函数、ScanMethod类的run函数。
1.scanPorts函数
scanPorts函数的形参包括主机名、端口范围、线程数、超时时间。scanPorts函数用于创建一个线程池,使用ExecutorService类的对象进行多线程异步执行。scanPorts函数中声明了ExecutorService类的threadPool对象,并调用了该对象的executor方法,以异步的方式调用ScanMethod类,执行多线程。具体代码出现在OwnTCPScan.java文件的818~839行。
2.ScanMethod类的构造函数
ScanMethod类同样拥有Runnable类的接口,其构造函数较为简单,仅实现传参作用。传递的参数包括主机名(或者主机IP地址)、待扫描的端口、线程数、超时时间。超时时间设置为0.8s。具体代码出现在OwnTCPScan.java文件的858~864行。
3.ScanMethod类的run函数
因为ScanMethod类拥有Runnable类的接口,因此必须实现抽象函数run()。通过主机IP地址和端口号,实例化SocketAddress类的对象socketAddress,建立套接字之后,再调用Socket类的connect函数,判断在规定的超时时间内能否连接上该端口。具体代码出现在OwnTCPScan.java文件的865~895行。
四、界面展示
运行说明
代码比较长,1000+。用户名为admin,密码为123456。password.txt中存放的是密码密文,而非登录密码的明文。初始内容是ADKIy3,只需要放在项目OwnTCPScan.java同级目录下。
五、完整代码
```java
@SuppressWarnings("serial")
public class OwnTCPScan extends JFrame implements ActionListener{
JPanel panel1;
//扫描类型
JLabel choiceTip=new JLabel("请选择扫描方式:");
protected static JRadioButton radioIp = new JRadioButton("根据IP地址扫描:");//IP地址单选按钮,默认选中IP地址
protected static JRadioButton radioHost = new JRadioButton("根据主机名与端口号扫描:",true);//主机名单选按钮
JLabel hostIP=new JLabel("(主机名或者主机IP地址)");
protected static ButtonGroup group1 = new ButtonGroup();
//IP地址扫描,十进制IP地址最多有12位,每3个一组,需要四个输入框
protected static JRadioButton radiosingle = new JRadioButton("单个IP地址");//IP地址单选按钮,默认选中IP地址
protected static JRadioButton radiomulti = new JRadioButton("一段IP地址",true);//主机名单选按钮
protected static ButtonGroup group3 = new ButtonGroup();
protected static JTextField singleip1=new JTextField("183",3);//输入扫描的起始IP地址前3位的可编辑文本框
protected static JTextField singleip2=new JTextField("232",3);//输入起始IP地址4~6位的可编辑文本框
protected static JTextField singleip3=new JTextField("231",3);//输入起始IP地址7~9位的输入框
protected static JTextField singleip4=new JTextField("172",3);//输入起始IP地址10~12位的输入
protected static JTextField fromip1=new JTextField("203",3);//输入扫描的起始IP地址前3位的可编辑文本框
protected static JTextField fromip2=new JTextField("208",3);//输入起始IP地址4~6位的可编辑文本框
protected static JTextField fromip3=new JTextField("40",3);//输入起始IP地址7~9位的输入框
protected static JTextField fromip4=new JTextField("110",3);//输入起始IP地址10~12位的输入
JLabel sPdot1 = new JLabel(".");
JLabel sPdot2 = new JLabel(".");
JLabel sPdot3 = new JLabel(".");//单个IP地址的分隔符,3个
JLabel P1=new JLabel("~");//一个文本显示框
JLabel Pdot1 = new JLabel(".");
JLabel Pdot2 = new JLabel(".");
JLabel Pdot3 = new JLabel(".");
JLabel Pdot4 = new JLabel(".");//IP地址段的分隔符,4个
protected static JTextField toip0=new JTextField("40",3);//输入扫描的结束IP地址第三部分
protected static JTextField toip=new JTextField("130",3);//输入扫描的结束IP地址最后4位的输入框,默认前3部分相同
//根据主机端口扫描
protected static JTextField hostname=new JTextField("LAPTOP-J860BMB3",10);//初始状态下显示本机的主机号,输入主机名的可编辑文本框,可输入10个字符
//分为所有端口和指定端口
protected static JRadioButton radioAll=new JRadioButton("常用端口",true);
JButton browser=new JButton("查看");
protected static JRadioButton radioPart=new JRadioButton("部分端口");
protected static ButtonGroup group2=new ButtonGroup();
//指定端口的两个输入
JLabel P2=new JLabel("~");
protected static JTextField minPort=new JTextField("0",4);//输入最小端口号的输入框,规定最多4位数,默认为0
protected static JTextField maxPort=new JTextField("1000",4);//输入最大端口的输入框,最多4位数,默认为1000
//线程数
JLabel THNUM=new JLabel("线程数:");
protected static JTextField maxThread=new JTextField("10",3);//输入最大线程数量的输入框,默认为10个线程,3位数
//定义按钮
protected static JButton Submit = new JButton("开始扫描");
protected static JButton Cancel = new JButton("退出");
protected static JButton Clear=new JButton("清空");
//扫描进度
//扫描结果
JLabel RST=new JLabel("扫描结果: ");
protected static JTextArea Result=new JTextArea();//显示文本,显示扫描结果
//菜单栏
protected static JMenu Menu0=new JMenu("文件(F)");//声明菜单项为文件
protected static JMenuItem menu0Change=new JMenuItem("修改密码(G)");//声明文件下的子菜单项,3个
protected static JMenuItem menu0Save=new JMenuItem("保存扫描结果(S)");
protected static JMenuItem menu0Exit=new JMenuItem("退出(Q)");
//主函数
public static void main(String[] args) {
LoginFrame lf=new LoginFrame();
lf.setVisible(true);
// OwnTCPScan ownTCPScan=new OwnTCPScan();//创建一个对象
// ownTCPScan.setVisible(true);//显示该对象的界面
}
public OwnTCPScan(){//构造函数,在构造函数中完成布局
super();
JMenuBar menuBar=new JMenuBar();
this.setTitle("Java端口扫描设计");
this.setResizable(false);
this.setSize(600,900);
this.setLocationRelativeTo(this.getOwner());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置主体完毕
//初始状态
singleip1.setEditable(false);
singleip2.setEditable(false);
singleip3.setEditable(false);
singleip4.setEditable(false);
fromip1.setEditable(false);
fromip2.setEditable(false);
fromip3.setEditable(false);
fromip4.setEditable(false);
toip0.setEditable(false);
toip.setEditable(false);//初始情况下,这些项不可以修改
radiosingle.setEnabled(false);
radiomulti.setEnabled(false);//初始情况下,这些项不可以选中
//设置界面
this.setJMenuBar(menuBar);//menuBar添加到主界面
menu0Change.addActionListener(this);
Menu0.add(menu0Change);//修改密码项添加进Menu0即文件下面
menu0Save.addActionListener(this);
menu0Save.setEnabled(false);//一开始,是灰的,不能点
Menu0.add(menu0Save);
menu0Exit.addActionListener(this);//从始至终,都可以退出
Menu0.add(menu0Exit);
Menu0.setMnemonic('F');//设置文件的快捷键为crtl+F
menu0Save.setMnemonic('S');//设置保存的快捷键为crtl+S
menu0Exit.setMnemonic('Q');//设置退出的快捷键为crtl+Q
menu0Change.setMnemonic('G');//设置更改密码的快捷键为crtl+G
menu0Change.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G,InputEvent.CTRL_MASK));
menu0Save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,InputEvent.CTRL_MASK));
menu0Exit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,InputEvent.CTRL_MASK));
menuBar.add(Menu0);//菜单栏建立完成
panel1 = new JPanel();
JScrollPane scroll = new JScrollPane(Result);
scroll.setPreferredSize(new Dimension(600,480));
Container pane = getContentPane();
getContentPane().add(panel1);
getContentPane().add(Result);
panel1.setPreferredSize(new Dimension(600, 420));
panel1.setLayout(null);//采用无布局方式添加控件
choiceTip.setBounds(30,30,100,20);
panel1.add(choiceTip);
group1.add(radioIp);
group1.add(radioHost);
radioIp.setBounds(30,60,200,20);
panel1.add(radioIp);
radioHost.setBounds(30,150,200,20);
panel1.add(radioHost);
group3.add(radiosingle);
group3.add(radiomulti);
radiosingle.setBounds(60,90,100,20);
panel1.add(radiosingle);
singleip1.setBounds(160,90,35,20);
panel1.add(singleip1);
sPdot1.setBounds(195,95,10,10);
panel1.add(sPdot1);
singleip2.setBounds(205,90,35,20);
panel1.add(singleip2);
sPdot2.setBounds(240,95,10,10);
panel1.add(sPdot2);
singleip3.setBounds(250,90,35,20);
panel1.add(singleip3);
sPdot3.setBounds(285,95,10,10);
panel1.add(sPdot3);
singleip4.setBounds(295,90,35,20);
panel1.add(singleip4);//单个IP地址的输入设计完成
radiomulti.setBounds(60,120,100,20);
panel1.add(radiomulti);
fromip1.setBounds(160,120,35,20);
panel1.add(fromip1);
Pdot1.setBounds(195,125,10,10);
panel1.add(Pdot1);
fromip2.setBounds(205,120,35,20);
panel1.add(fromip2);
Pdot2.setBounds(240,125,10,10);
panel1.add(Pdot2);
fromip3.setBounds(250,120,35,20);
panel1.add(fromip3);
Pdot3.setBounds(285,125,10,10);
panel1.add(Pdot3);
fromip4.setBounds(295,120,35,20);
panel1.add(fromip4);
P1.setBounds(330,120,10,20);
panel1.add(P1);
toip0.setBounds(340,120,35,20);
panel1.add(toip0);
Pdot4.setBounds(375,125,10,10);
panel1.add(Pdot4);
toip.setBounds(385,120,35,20);
panel1.add(toip);//一段IP地址设计完成
hostname.setBounds(230,150,150,20);
panel1.add(hostname);
hostIP.setBounds(380,150,150,15);
panel1.add(hostIP);
group2.add(radioAll);
group2.add(radioPart);
radioAll.setBounds(60,180,120,20);
panel1.add(radioAll);
browser.setBounds(180,180,60,20);
panel1.add(browser);
browser.setFont(new Font("",Font.BOLD,12));
//常用端口设计完成
radioPart.setBounds(60,210,120,20);
panel1.add(radioPart);
minPort.setBounds(180,210,40,20);
panel1.add(minPort);
P2.setBounds(220,210,10,20);
panel1.add(P2);
maxPort.setBounds(230,210,40,20);//指定端口设计完成
panel1.add(maxPort);
THNUM.setBounds(30,240,60,20);
panel1.add(THNUM);
maxThread.setBounds(90,240,60,20);
panel1.add(maxThread);//线程数设计完成
Submit.setBounds(80,290,120,30);
panel1.add(Submit);
Cancel.setBounds(240,290,120,30);
panel1.add(Cancel);
Clear.setBounds(400,290,120,30);
panel1.add(Clear);//三个按钮设计完成
RST.setBounds(30,350,120,20);
panel1.add(RST);//扫描结果提示语句设计完成,至此,panel1建立完成
pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));//下面是滚动条,即结果显示区域
Result.setLayout(null);
scroll.setViewportView(Result);
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
pane.add(scroll);
Result.setEditable(true);//结果显示区域可编辑
Result.setTabSize(10);
Result.setLineWrap(true);//激活自动换行功能
Result.setWrapStyleWord(true);//激活断行不断字功能
Result.setBounds(40,420,500,200);
Result.setFont(new Font("宋体",Font.BOLD,14));//面板建立完成
//按钮的监听事件
//两组单选按钮需要互斥,因此设置监听事件
radioIp.addItemListener(new ItemListener() {//Host的三个项都不可以修改
@Override
public void itemStateChanged(ItemEvent arg0) {
if(arg0.getSource()==radioIp){
fromip1.setEditable(true);
fromip2.setEditable(true);
fromip3.setEditable(true);
fromip4.setEditable(true);
toip.setEditable(true);
toip0.setEditable(true);
singleip1.setEditable(true);
singleip2.setEditable(true);
singleip3.setEditable(true);
singleip4.setEditable(true);//IP下,这些项可以修改
radiosingle.setEnabled(true);
radiomulti.setEnabled(true);//IP下,可以选中
hostname.setEditable(false);
minPort.setEditable(false);
maxPort.setEditable(false);//IP下,这三项不可以修改
radioAll.setEnabled(false);
radioPart.setEnabled(false);
}
}
});
radioHost.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent arg0) {
if(arg0.getSource()==radioHost){
OwnTCPScan.maxThread.setText("10");
fromip1.setEditable(false);
fromip2.setEditable(false);
fromip3.setEditable(false);
fromip4.setEditable(false);
toip.setEditable(false);
toip0.setEditable(false);
singleip1.setEditable(false);
singleip2.setEditable(false);
singleip3.setEditable(false);
singleip4.setEditable(false);//Host下,不可以修改
radiosingle.setEnabled(false);
radiomulti.setEnabled(false);//host下,不可以选中
hostname.setEditable(true);
minPort.setEditable(true);
maxPort.setEditable(true);//Host下,这三项恢复修改
radioAll.setEnabled(true);
radioPart.setEnabled(true);
}
}
});
radiosingle.addItemListener(new ItemListener() {//选择单个IP地址,默认状态,不可修改IP范围下的输入
@Override
public void itemStateChanged(ItemEvent arg0) {
OwnTCPScan.maxThread.setText("1");
fromip1.setEditable(false);
fromip2.setEditable(false);
fromip3.setEditable(false);
fromip4.setEditable(false);
toip.setEditable(false);
toip0.setEditable(false);
singleip1.setEditable(true);
singleip2.setEditable(true);
singleip3.setEditable(true);
singleip4.setEditable(true);
}
});
radiomulti.addItemListener(new ItemListener() {//选中IP范围下,不可修改单个IP的输入
@Override
public void itemStateChanged(ItemEvent arg0) {
OwnTCPScan.maxThread.setText("10");
fromip1.setEditable(true);
fromip2.setEditable(true);
fromip3.setEditable(true);
fromip4.setEditable(true);
toip.setEditable(true);
toip0.setEditable(true);
singleip1.setEditable(false);
singleip2.setEditable(false);
singleip3.setEditable(false);
singleip4.setEditable(false);
}
});
radioAll.addItemListener(new ItemListener() {//选择常用端口,最小最大不可编辑
@Override
public void itemStateChanged(ItemEvent arg0) {
minPort.setEditable(false);
maxPort.setEditable(false);
}
});
radioPart.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent arg0) {
minPort.setEditable(true);
maxPort.setEditable(true);
}
});
browser.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
JOptionPane.showMessageDialog(null, "常用端口包括:\n21, 22, 23, 25, 26, 53,69, 80, 110, 143,443,465,69,161,162,\r\n" +
" 135,995,1080,1158,1433,1521,2100, 3128, 3306, 3389,7001, 8080,\r\n" +
" 8081, 9080, 9090, 43958 ,135,445,1025,1026,1027,1028,1055,5357",
"查看常用端口", JOptionPane.INFORMATION_MESSAGE);
}
});
Submit.addActionListener(new ActionListener() {//开始扫描的点击事件,调用一个类
@Override
public void actionPerformed(ActionEvent arg0) {
SubmitAction submitAction=new SubmitAction();
submitAction.actionPerformed(arg0);
}
});//扫描按钮的实现,是最重要的部分,里面包含合法性判别与扫描过程
Cancel.addActionListener(new ActionListener() {//取消按钮的实现,直接退出,调用exitDialog()函数
@Override
public void actionPerformed(ActionEvent arg0) {
exitDialog();//调用退出对话框函数
}
});
Clear.addActionListener(new ActionListener() {//清空按钮的实现,将界面完全清空
@Override
public void actionPerformed(ActionEvent arg0) {
radioIp.setSelected(false);
radioHost.setSelected(true);
radioAll.setSelected(true);
singleip1.setText("");
singleip2.setText("");
singleip3.setText("");
singleip4.setText("");
fromip1.setText("");
fromip2.setText("");
fromip3.setText("");
fromip4.setText("");
toip.setText("");
toip0.setText("");
hostname.setText("");
minPort.setText("");
maxPort.setText("");
maxThread.setText("");
Result.setText("");
}
});
}
//菜单选项的3个点击事件
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==menu0Save){
menu0Save.setEnabled(true);
JFileChooser fc=new JFileChooser();
int returnVal=fc.showSaveDialog(null);
//点击“保存”
if(returnVal == 0){
File saveFile=fc.getSelectedFile();
try {
FileWriter writeOut = new FileWriter(saveFile);
writeOut.write(OwnTCPScan.Result.getText());
writeOut.close();
}
catch (IOException ex) {
ex.printStackTrace();
System.out.println("保存失败");
}
}
//点击“取消”
else return;
JOptionPane.showMessageDialog(null, "您已经成功保存数据","保存", JOptionPane.INFORMATION_MESSAGE);
}
if(e.getSource()==menu0Change){
String s=JOptionPane.showInputDialog(null, "请输入新密码:", "修改密码", JOptionPane.PLAIN_MESSAGE);
if(s==null)
return;
s=s.trim();
if(s.length()==0){
JOptionPane.showMessageDialog(null, "密码不能为空!", "修改密码", JOptionPane.ERROR_MESSAGE);
return;
}
String clearText = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
String cipherText= "UADKIy3FxgVkl5iZzWuGd1HNhOCtvjJ2pEn6Yw7PqrcQReB8Mfm0STsLX9a4ob";
String resultText= "";
for(int i=0;i<s.length();i++){
char c=s.charAt(i);
if(clearText.indexOf(""+c)==-1){
JOptionPane.showMessageDialog(null, "密码中包含非法字符", "修改密码", JOptionPane.ERROR_MESSAGE);
return;
}else{
resultText+=""+cipherText.charAt(clearText.indexOf(""+c));
}
}
try {
FileOutputStream fos = new FileOutputStream("./src/password.txt");
OutputStreamWriter dos=new OutputStreamWriter(fos);
BufferedWriter writer=new BufferedWriter(dos);
writer.write(resultText);
writer.close();dos.close();fos.close();
JOptionPane.showMessageDialog(null, "密码修改成功!", "学生信息管理系统", JOptionPane.INFORMATION_MESSAGE);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if(e.getSource()==menu0Exit){
exitDialog();
}
}
protected void noteDialog(String tip){//调用时需要传参,传递错误信息是什么
JOptionPane.showMessageDialog(null, tip, "错误", JOptionPane.ERROR_MESSAGE);
}
//实现“开始扫描”功能,完成扫描,含所有输入合法的判断,以及端口扫描的入口函数scanPorts,进行参数传递
class SubmitAction implements ActionListener{
public SubmitAction() {
}
private static final int MAXTHREAD=100;//最大线程数设为100
public void noteDialog(String tip){//调用时需要传参,传递错误信息是什么
JOptionPane.showMessageDialog(null, tip, "错误", JOptionPane.ERROR_MESSAGE);
}
public void actionPerformed (ActionEvent a){//点击确定按钮后要做的事情
//首先判断输入合法性,包括五个IP地址是否是整型数,是否是有效数0~255;端口是否是整型数
long startTime=System.currentTimeMillis();//获取开始时间
long endTime=0;//结束时间初始化
int minPort;
int maxPort;//端口扫描用的
int maxThread;
int sip1=0;
int sip2=0;
int sip3=0;
int sip4=0;//单个IP地址用的
int ip1 = 0;
int ip2 = 0;
int ip3 = 0;
int ipstart = 0;
int ipend0 = 0;
int ipend = 0;//用这些变量代指,编码方便,IP范围用的
String hostname = "";
OwnTCPScan.Result.setText("");
OwnTCPScan.Submit.setEnabled(false);
OwnTCPScan.menu0Save.setEnabled(false);//将"确定"按钮和保存按钮设置成为不可用
if(OwnTCPScan.radioIp.isSelected()){//根据IP地址扫描,默认是主句号与端口
if(OwnTCPScan.radiomulti.isSelected()){//如果是扫描一段IP地址范围
//逐位判断IP地址是否合法,如果非法,则直接退出
try{//判断IP的前3位是否为int型
ip1=Integer.parseInt(OwnTCPScan.fromip1.getText().trim());
}
catch(NumberFormatException e){
noteDialog("起始IP地址的第一部分必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断IP的4~6位是否为int型
ip2=Integer.parseInt(OwnTCPScan.fromip2.getText().trim());
}
catch(NumberFormatException e){
noteDialog("起始IP地址的第二部分必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断IP的7~9位是否为int型
ip3=Integer.parseInt(OwnTCPScan.fromip3.getText().trim());
}
catch(NumberFormatException e){
noteDialog("起始IP地址的第三部分必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断起始IP的最后4位是否为int型
ipstart=Integer.parseInt(OwnTCPScan.fromip4.getText().trim());
}
catch(NumberFormatException e){
noteDialog("起始IP地址的第四部分必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断目标IP的最后4位是否为int型
ipend0=Integer.parseInt(OwnTCPScan.toip0.getText().trim());
ipend=Integer.parseInt(OwnTCPScan.toip.getText().trim());
}
catch(NumberFormatException e){
noteDialog("结束IP地址必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断进程数的有效性
maxThread=Integer.parseInt(OwnTCPScan.maxThread.getText().trim());
}
catch(NumberFormatException e){
noteDialog("线程数必须存在,并且是整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}//合法性判断完成,是整数
//判断条件:大于0且小于等于255
if(ip1<0 || ip1>255||ip2<0 || ip2>255||ip3<0 || ip3>255||ipstart<0 || ipstart>255 ||ipend<0 || ipend>255 ||ipend0<0 || ipend0>255){
noteDialog("IP地址必须是0~255之间的整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
//判断from是否比to要小
if(ip3>ipend0) {
noteDialog("起始扫描地址要小于终止扫描地址!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
if(ip3==ipend0&&ipstart>ipend) {
noteDialog("起始扫描地址要小于终止扫描地址!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
if(maxThread<1 || maxThread>MAXTHREAD){//判断线程数量的有效范围判断条件:大于1且小于200
noteDialog("线程数为1-"+MAXTHREAD+"之间的整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}//有效性判断完成,是符合条件的数
else{//说明上面6个IP地址都对,且线程数正确,开始扫描IP地址,调用PingTester类,所有的内容都在这个类中
OwnTCPScan.Result.append("线程数 "+OwnTCPScan.maxThread.getText()+"\n");
PingTester tester = new PingTester(ip1,ip2,ip3,ipstart,ipend0,ipend);
tester.startPing(maxThread);//读进来的最大线程数
OwnTCPScan.Submit.setEnabled(true);//允许重新扫描
OwnTCPScan.menu0Save.setEnabled(true);//允许保存
endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("扫描时长为:"+(double)(endTime-startTime)/1000+"s");
OwnTCPScan.Result.append("扫描时长为:"+(double)(endTime-startTime)/1000+"s\n");
}//执行结束
}//一段IP地址的扫描结束
if(OwnTCPScan.radiosingle.isSelected()){//选择单个IP地址扫描
try{//判断IP的前3位是否为int型
sip1=Integer.parseInt(OwnTCPScan.singleip1.getText().trim());
}
catch(NumberFormatException e){
noteDialog("起始IP地址的第一部分必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断IP的4~6位是否为int型
sip2=Integer.parseInt(OwnTCPScan.singleip2.getText().trim());
}
catch(NumberFormatException e){
noteDialog("起始IP地址的第二部分必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断IP的7~9位是否为int型
sip3=Integer.parseInt(OwnTCPScan.singleip3.getText().trim());
}
catch(NumberFormatException e){
noteDialog("起始IP地址的第三部分必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断IP的最后4位是否为int型
sip4=Integer.parseInt(OwnTCPScan.singleip4.getText().trim());
}
catch(NumberFormatException e){
noteDialog("起始IP地址的第四部分必须存在,且为整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断进程数的有效性,这个不变
maxThread=Integer.parseInt(OwnTCPScan.maxThread.getText().trim());
}
catch(NumberFormatException e){
noteDialog("线程数必须存在,并且是整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}//输入合法性判断结束,是整数
//判断条件:大于0且小于等于255
if(sip1<0 || sip1>255||sip2<0 || sip2>255||sip3<0 || sip3>255||sip4<0 ||sip4>255){
noteDialog("IP地址必须是0~255之间的整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
if(maxThread<1 || maxThread>MAXTHREAD){//判断线程数量的有效范围判断条件:大于1且小于200
noteDialog("线程数为1-"+MAXTHREAD+"之间的整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}//输入有效性判断结束,是符合条件的数
else{//说明上面4个IP地址都对,且线程数正确,开始扫描IP地址
OwnTCPScan.Result.append("线程数 "+OwnTCPScan.maxThread.getText()+"\n");
PingTester tester = new PingTester(sip1,sip2,sip3,sip4,sip3,sip4);
tester.startPing(maxThread);//读进来的最大线程数
OwnTCPScan.Submit.setEnabled(true);
OwnTCPScan.menu0Save.setEnabled(true);
endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("扫描时长为:"+(double)(endTime-startTime)/1000+"s");
OwnTCPScan.Result.append("扫描时长为:"+(double)(endTime-startTime)/1000+"s\n");
}
}//单个IP地址扫描结束
}//根据IP地址扫描结束,最外层结束
if(OwnTCPScan.radioHost.isSelected()){//根据主机名进行端口扫描
try{//判断主机名称的有效性
@SuppressWarnings("unused")
InetAddress ia=InetAddress.getByName(OwnTCPScan.hostname.getText().trim());
}
catch(UnknownHostException e){
noteDialog("主机名无效,网络不可达!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
try{//判断线程数的有效性,整数
maxThread=Integer.parseInt(OwnTCPScan.maxThread.getText().trim());
}
catch(NumberFormatException e){
noteDialog("线程数必须存在,并且是整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
if(maxThread<1 || maxThread>MAXTHREAD){//判断线程数量的有效范围判断条件:大于1且小于200
noteDialog("线程数为1-"+MAXTHREAD+"之间的整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}//常用端口的有效性判断完成
if(OwnTCPScan.radioAll.isSelected()){//判断选中的常用端口还是指定端口
//很简单,直接进行扫描端口
hostname=OwnTCPScan.hostname.getText();
List <Integer>portList = new LinkedList<Integer>();// 定义一个List容器表示扫描的端口的List集合
//之所以选择LinkedList,因为进行增删操作,linkedList较为容易,只需要移动指针
Integer[] ports = new Integer[]{21, 22, 23, 25, 26, 53,69, 80, 110, 143,
443,465,69,161,162,135,995,1080,1158,1433,1521,2100, 3128, 3306, 3389,
7001, 8080, 8081, 9080, 9090, 43958 ,135,445,1025,1026,1027,1028,1055,5357};
portList.addAll(Arrays.asList(ports));
//scanPorts为SubmitAction类的内部函数,调用该函数,该函数调用ScanMethod类
scanPorts(hostname, portList,maxThread, 800);//将主机名,常用端口,线程数与超时时间800ms传参过去
OwnTCPScan.Submit.setEnabled(true);
OwnTCPScan.menu0Save.setEnabled(true);
endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("\n扫描完成\n");
OwnTCPScan.Result.append("\n扫描完成\n");
System.out.println("扫描时长为:"+(double)(endTime-startTime)/1000+"s");
OwnTCPScan.Result.append("扫描时长为:"+(double)(endTime-startTime)/1000+"s\n");
}
if(OwnTCPScan.radioPart.isSelected()){//指定端口
//需要继续判断端口输入的有效性
try{//判断端口号的有效性,整数
minPort=Integer.parseInt(OwnTCPScan.minPort.getText().trim());
maxPort=Integer.parseInt(OwnTCPScan.maxPort.getText().trim());
}
catch(NumberFormatException e){
noteDialog("端口号必须存在,并且是整数!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
//判断最小端口号的有效范围
//判断条件:大于0且小于65535,最大端口应大于最小端口
if(minPort<0 || minPort>65535 || minPort>maxPort){
noteDialog("最小端口在0-65535之间,且小于最大端口数"+maxPort+"!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
else{//最小端口有效,什么也不做
}
if(maxPort<0 || maxPort>65535){//判断最大端口号的有效范围;判断条件:大于0且小于65535,最大端口应大于最小端口
noteDialog("最大端口在0-65535之间,且大于最小端口数"+minPort+"!");
OwnTCPScan.Submit.setEnabled(true);
return;
}
else{//最大端口号有效,进行端口扫描
hostname=OwnTCPScan.hostname.getText().trim();
List <Integer>portList = new LinkedList<Integer>();// 定义一个List容器表示扫描的团口的List集合
int length_integer=maxPort-minPort+1;
Integer[] ports = new Integer[length_integer];
for (int i=0;i<maxPort-minPort+1;i++){
ports[i]=minPort+i;
}
portList.addAll(Arrays.asList(ports));
scanPorts(hostname, portList,maxThread, 800);//将主机名,端口范围,线程数与超时时间800ms传参过去
OwnTCPScan.Submit.setEnabled(true);
OwnTCPScan.menu0Save.setEnabled(true);
System.out.println("\n扫描完成\n");
OwnTCPScan.Result.append("\n扫描完成\n");
endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("扫描时长为:"+(double)(endTime-startTime)/1000+"s");
OwnTCPScan.Result.append("扫描时长为:"+(double)(endTime-startTime)/1000+"s\n");
}
}
}//主机号端口判断结束
}
//扫描端口的方法实现
public void scanPorts(String ip, List<Integer> portSet,int threadNumber, int timeout) {//扫描端口,用到ScanMethod类
ExecutorService threadPool = Executors.newCachedThreadPool();//负责线程的使用和调度的根接口,异步执行机制
//线程池
for (int i = 0; i < threadNumber; i++) {
//10个线程 加入到线程池里
ScanMethod scanMethod2 = new ScanMethod(ip, portSet,threadNumber, i, timeout);
//层层传参,传到ScanMethod里面进行端口扫描的实现
threadPool.execute(scanMethod2);//异步执行
}
threadPool.shutdown();
while (true) {
if (threadPool.isTerminated()) {
//如果所有任务都在关闭后完成,则返回true。注意,除非首先调用了shutdown或shutdownNow,否则isTerminated永远不会为真。
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}// end of while
}
}
//退出功能的提示对话框,使用两次
private void exitDialog(){
int n = JOptionPane.showConfirmDialog(null, "确认退出吗?", "退出系统", JOptionPane.YES_NO_OPTION);
if (n == JOptionPane.YES_OPTION) {
System.exit(0);
} else if (n == JOptionPane.NO_OPTION) {//关闭对话框,什么都不做
}
}
//扫描端口类,含run方法的实现
private class ScanMethod implements Runnable {
private String ip; // 目标IP
private List<Integer> portList; // 待扫描的端口的List集合
private int threadNumber, serial, timeout; // 线程数,这是第几个线程,超时时间
public ScanMethod(String ip, List<Integer> portList, int threadNumber,int serial, int timeout) {
this.ip = ip;//IP地址或主机号
this.portList = portList;//待扫描端口的LIST集合
this.threadNumber = threadNumber;
this.serial = serial;//第几个线程
this.timeout = timeout;//连接超时时间
}
public void run() {
int port = 0;
Integer[] ports = portList.toArray(new Integer[portList.size()]); // List转数组
try {
InetAddress address = InetAddress.getByName(ip); //InetAddress可以根据主机名获取主机的ip地址,也支持输入IP地址
Socket socket;//定义套接字,调用connect函数查看端口的状态
SocketAddress socketAddress;//SocketAddress是一个抽象类,传递ip和端口,其实真正实现的不是SocketAddress,而是其子类,这样声明是为了方便调用
//必须使用SocketAddress的子类来建立SocketAddress对象
if (ports.length < 1)
return;//若数组没有元素,返回,不执行
for (port = 0 + serial; port <= ports.length - 1; port += threadNumber) {//每次运行maxThread个线程
socket = new Socket();
socketAddress = new InetSocketAddress(address, ports[port]);
//InetSocketAddress是SocketAddress的实现子类,此类实现 IP 套接字地址(IP 地址 + 端口号),不依赖任何协议
try {
socket.connect(socketAddress, timeout);
//对目标主机的指定端口进行连接,超时后连接失败
socket.close();
//关闭端口
System.out.println("端口 " + ports[port] + " :开放");
OwnTCPScan.Result.append("端口 " + ports[port] + " :开放\n");
//在文本区域里更新消息
} catch (IOException e) {
System.out.println("端口 " + ports[port] + " :关闭");
//OwnTCPScan.Result.append("端口 " + ports[port] + " :关闭\n");//产生异常表示端口关闭
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}//end of run()
}//end of ScanMethod
//IP地址扫描类,含PingRunner内部类和startPing方法
class PingTester {
private Queue<String> allIp;
private int fetchedNum = 0; // 已经取得的任务数量,每次从队列中取一个ip就加1
public PingTester(int fromip1,int fromip2,int fromip3,int fromip4,int toip3,int toip4) {
// 首先创建一个队列用于存储所有ip地址
allIp = new LinkedList<String>();
int finalip4=0;
for (int i =fromip3; i <= toip3; i++) {
if(i<toip3)
finalip4=255;
else
finalip4=toip4;
for (int j=fromip4; j <=finalip4; j++) {
allIp.offer(fromip1+"."+fromip2+"."+i+"."+j);//插入到队列中
//offer()方法是往队列尾部插入元素,如果越界直接返回false,与add方法类似,但是稍有不同
}
}
}
public void startPing(int threadNum) {
// 创建一个线程池,多个线程同时跑
ExecutorService executor = Executors.newFixedThreadPool(threadNum);//异步执行机制
//提供用于管理终止的方法的执行程序,以及可以产生用于跟踪一个或多个异步任务进展的未来的方法。
for (int i = 0; i < threadNum; i++) {
executor.execute(new PingRunner());//异步执行,调用线程类,实现多线程
}
executor.shutdown();//必须先shutdown,否则isTerminated永远不会为真
try {
while (!executor.isTerminated()) {
//如果所有任务都在关闭后完成,则返回true。注意,除非首先调用了shutdown或shutdownNow,否则isTerminated永远不会为真。
Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
//还需要将这个数字输出出来
System.out.println("获取的地址总数是"+fetchedNum);
OwnTCPScan.Result.append("获取的地址总数是"+fetchedNum+"\n");
System.out.println("扫描完成");
OwnTCPScan.Result.append("\n扫描完成\n");
}
//PingTester的内部类,有Runnable的接口
private class PingRunner implements Runnable {
private String taskIp = null;
@Override
public void run() {//抽象方法的实现
try {
while ((taskIp = getIp()) != null) {//调用getIp()方法得到IP地址
InetAddress addr = InetAddress.getByName(taskIp);//获得IP地址
if (addr.isReachable(5000)) {//测试该地址是否可到达。实现会尽最大努力尝试访问主机,访问超时时间为5s
//说明可达,需要添加进去,显示出来
System.out.println("IP地址 ["+taskIp+"]是可达的");
OwnTCPScan.Result.append("IP地址 ["+taskIp+"]是可达的"+"\n");
} else {
System.out.println("IP地址 ["+taskIp+"]是不可达的");
//OwnTCPScan.Result.append("IP地址 ["+taskIp+"]是不可达的"+"\n");
}
}
} catch (SocketException e) {
System.out.println("IP地址 ["+taskIp+"]没有访问权限");
OwnTCPScan.Result.append("IP地址 ["+taskIp+"]没有访问权限"+"\n");
} catch (Exception e) {
System.out.println("---------------------------------------------"+taskIp);
OwnTCPScan.Result.append("---------------------------------------------"+taskIp+"\n");
e.printStackTrace();
}
}
public String getIp() {
String ip = null;
synchronized (allIp) {//对象锁
ip = allIp.poll();//从头部删除一个元素,poll() ,在队列元素为空的情况下,返回 null
}
if (ip != null) {
fetchedNum++;//扫描的IP地址数增1
}
return ip;
}
}
}
}
//登录界面类
@SuppressWarnings("serial")
class LoginFrame extends JFrame implements ActionListener{
JTextField username=new JTextField(10);//用户编辑框,输入用户名
JPasswordField pwd=new JPasswordField(10);//输入密码
LoginFrame(){
Font font1=new Font("标楷体",Font.BOLD,16);//提示语的字体
Font font2=new Font("Times new Roman",Font.PLAIN,16);//用户输入的字体
this.setTitle("系统登录");
this.setResizable(false);//不允许缩放
this.setSize(400,260);//界面大小
this.setLocationRelativeTo(this.getOwner());//相对位置,居中
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击×则结束进程
JLabel lb1=new JLabel("用户名");
JLabel lb2=new JLabel("密 码");
JButton btn=new JButton("登录");
btn.addActionListener(this);//btn的点击事件
Container c=this.getContentPane();//只有一个界面
c.setLayout(null);//不使用任何现存布局
lb1.setBounds(80, 40, 60, 30);//用户名提示
lb1.setFont(font1);
lb1.setForeground(Color.black);
c.add(lb1);
username.setBounds(150,40,150,30);//用户名输入框
username.addActionListener(this);
username.setFont(font2);
username.setForeground(Color.black);
c.add(username);
lb2.setBounds(80,80,60,30);//密码提示
lb2.setFont(font1);
lb2.setForeground(Color.black);
c.add(lb2);
pwd.setBounds(150,80,150,30);//密码输入框
pwd.addActionListener(this);
pwd.setFont(font2);
pwd.setForeground(Color.black);
c.add(pwd);
btn.setBounds(110,130,150,40);//登录按钮
btn.setFont(font1);
c.add(btn);
this.setVisible(true);
this.username.requestFocus();
}
public void actionPerformed(ActionEvent e) {
if(e.getSource()==this.username){
this.pwd.requestFocus();
return;
}
try {
FileInputStream fis;
fis = new FileInputStream("./src/password.txt");
InputStreamReader dis=new InputStreamReader(fis);
BufferedReader reader=new BufferedReader(dis);
String s;
String clearText = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
String cipherText= "UADKIy3FxgVkl5iZzWuGd1HNhOCtvjJ2pEn6Yw7PqrcQReB8Mfm0STsLX9a4ob";
if((s=reader.readLine())!=null){
if(username.getText().trim().equals("admin")){//用户名正确,比较密码
boolean isCorrect=true;
char[] ch1=pwd.getPassword();
char[] ch2=s.toCharArray();
if(ch1.length==ch2.length){//如果长度相等,再比较
for(int i=0;i<ch1.length;i++){
if(clearText.indexOf(ch1[i])!=cipherText.indexOf(ch2[i])){//只要有不等的,就为错
isCorrect=false;
break;
}
}
}
else{//长度不等,直接错误
isCorrect=false;
}
//对结果做出判断
if(isCorrect){
this.setVisible(false);
this.dispose();
JOptionPane.showMessageDialog(null,"登录成功!","用户登录",JOptionPane.OK_CANCEL_OPTION);
OwnTCPScan ownTCPThread=new OwnTCPScan();//创建一个对象
ownTCPThread.setVisible(true);//显示该对象的界面
}
else{
JOptionPane.showMessageDialog(null, "您填写的密码不正确", "用户登录", JOptionPane.WARNING_MESSAGE);
}
}else{//用户名不正确
JOptionPane.showMessageDialog(null, "您填写的用户名不正确", "用户登录", JOptionPane.WARNING_MESSAGE);
this.username.requestFocus();
}
}
reader.close();
dis.close();
fis.close();
}catch (IOException e1) {
e1.printStackTrace();
}
}
}