一 原理解释
这里所说的服务器类型是指像Apache,tomcat,nginx,IIS这种。其中原理用到了HTTP Header的Responses,这里面有项叫“Server”的参数就包涵我们所需要的信息。下面是Responses的部分截图:
(PS:更多相关可自行百度“HTTP Header”)
因此,我们想要做一个多线程批量探测的软件,思路有两种:(1)根据别人提供的接口然后我们去调用获取(比如:http://api.builtwith.com 这个我以后可能会写);(2)针对每个IP我们发送Get请求,然后去获取响应头文件中的Server参数
PS:文末我会放出打包好的有GUI界面的jar文件以及完整源代码
二 项目结构
这里我选择了第二种方式,自己动手做一个,虽然获取到的信息没有用接口获取的来的全。下面是整个完整小项目的目录结构:
三 核心代码
在这里核心代码在ServerTypeDemo.java这个文件中,主要是通过对指定IP以及端口发出Get请求,然后获取响应包中的“Server”,最后将结果写入文件。代码如下:
package action; import java.io.BufferedWriter; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.Map; public class ServerTypeDemo{ /** * 获取到的服务器类型写入txt * @param ip IP * @param port 提交端口 * @param writer 写入流 * * @return null * */ public static void savaData(String ip,String port,BufferedWriter writer){ try { URL url = new URL("http://" + ip + ":" + port); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(1000); //毫秒 connection.setReadTimeout(1000); Map<String, List<String>> map = connection.getHeaderFields(); //获取HTTP Header Responses List<String> server = map.get("Server"); //关键点 if(server == null){ return; } else{ //写入文件 for(String tmp : server){ writer.write(ip + ":" + port + " " + tmp); writer.newLine(); } writer.flush(); connection.disconnect(); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
四 一个简陋界面
从左到右分别填:起始IP,结束IP(PS:在这里两个IP不在一个C段也行,比如:192.168.1.1~192.168.255.255),线程数,最后点击开始进行扫描,待全部线程结束后会给出提示信息。界面相关代码如下:
package view; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.Format; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import util.IPTraverse; import action.MyThread; public class MainView extends JFrame implements ActionListener { /** * 此程序是为了批量探测目标IP段的服务器类型(Apache,tomcat,nginx,IIS。。。) 其中用到了线程池,可以自定义扫描线程数量 * (PS:只做了一个简陋的界面 O(∩_∩)O~) * * @author zifangsky * @blog http://www.zifangsky.cn * @version V1.0.0 * @date 2015-12-9 * */ private static final long serialVersionUID = 1L; private JPanel mainJPanel; private JTextField start, end, threadNum; // 起始IP,结束IP,线程值 private JButton submit; private JMenuBar jMenuBar; private JMenu help; // 帮助 private JMenuItem author, contact, version, readme; // 作者,邮箱,版本号,使用说明 private Font font = new Font("宋体", Font.LAYOUT_NO_LIMIT_CONTEXT, 16); public MainView() { super("批量判断服务器类型(Apache,tomcat,nginx。。。) by zifangsky"); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // 屏幕大小 setPreferredSize(new Dimension(600, 300)); int frameWidth = this.getPreferredSize().width; // 界面宽度 int frameHeight = this.getPreferredSize().height; // 界面高度 setSize(frameWidth, frameHeight); setLocation((screenSize.width - frameWidth) / 2, (screenSize.height - frameHeight) / 2); mainJPanel = new JPanel(); start = new JTextField("192.168.1.1", 12); end = new JTextField("192.168.1.254", 12); threadNum = new JTextField("5", 8); submit = new JButton("开始"); submit.setFont(font); jMenuBar = new JMenuBar(); help = new JMenu("帮助"); help.setFont(font); author = new JMenuItem("作者"); author.setFont(font); contact = new JMenuItem("联系方式"); contact.setFont(font); version = new JMenuItem("版本"); version.setFont(font); readme = new JMenuItem("使用说明"); readme.setFont(font); mainJPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 15, 40)); mainJPanel.add(start); mainJPanel.add(end); mainJPanel.add(threadNum); mainJPanel.add(submit); jMenuBar.add(help); help.add(author); help.add(contact); help.add(version); help.add(readme); add(mainJPanel); setJMenuBar(jMenuBar); setVisible(true); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); submit.addActionListener(this); author.addActionListener(this); contact.addActionListener(this); version.addActionListener(this); readme.addActionListener(this); } public static void main(String[] args) { new MainView(); }
五 处理点击事件
public void actionPerformed(ActionEvent e) { if (e.getSource() == submit) { Date date = new Date(); Format format = new SimpleDateFormat("HH_mm_ss"); // 结果存储的文件名 String fileName = format.format(date) + ".txt"; String startIP = start.getText(); String endIP = end.getText(); long sips = IPTraverse.ipToLong(startIP); long eips = IPTraverse.ipToLong(endIP); int threadNumber = Integer.valueOf(threadNum.getText()); // 多线程,线程池 ExecutorService eService = Executors.newFixedThreadPool(50); for (int i = 0; i < threadNumber; i++) { MyThread myThread = new MyThread(sips, eips, i, threadNumber, fileName); eService.execute(myThread); } eService.shutdown(); while (true) { //判断是否全部线程都已经执行结束了 if (eService.isTerminated()) { JOptionPane.showMessageDialog(this, "全部扫描结束", "提示:", JOptionPane.INFORMATION_MESSAGE); break; } try { Thread.sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } } } else if (e.getSource() == author) { JOptionPane.showMessageDialog(this, "zifangsky", "作者:", JOptionPane.INFORMATION_MESSAGE); } else if (e.getSource() == contact) { JOptionPane.showMessageDialog(this, "邮箱:[email protected]\n博客:http://www.zifangsky.cn", "联系方式:", JOptionPane.INFORMATION_MESSAGE); } else if (e.getSource() == version) { JOptionPane.showMessageDialog(this, "v1.0.0", "版本号:", JOptionPane.INFORMATION_MESSAGE); } else if (e.getSource() == readme) { JOptionPane .showMessageDialog( this, "我只做了一个简陋的图像化界面,默认只扫描80和8080端口,从左到右分别填起始ip(比如:192.168.0.1)\n;" + "结束ip(比如:192.168.250.250);线程数目(别太大,不然有的结果就漏掉了)\n" + "还有就是执行的结果会保存在当前目录下的一个txt文件中", "使用说明:", JOptionPane.INFORMATION_MESSAGE); } } }
在这里,由于单线程的扫描速度很慢,因此我使用了多线程。同时又为了判断全部线程是否都已经执行完毕,我又将这些线程放在了一个线程池里,通过eService.isTerminated()这个方法来判断任务是否全部执行完毕。
其实,这里还有一个关键点,如何快速的遍历一个IP段之间的每个IP?最开始我使用了笨方法(PS:四层for循环依次判断),后来度娘了一下,找到了一个不错的IP工具类,可以将IP在long和String之间相互转化。因此转化成long时,一层for循环就可以遍历了,需要String类型时再将它转化回去就可以了
IPTraverse.java:
package util; public class IPTraverse { /** * 将127.0.0.1形式的IP地址转换成十进制整数 * @param strIp * @return 整数 * */ public static long ipToLong(String strIp) { long[] ip = new long[4]; // 先找到IP地址字符串中.的位置 int position1 = strIp.indexOf("."); int position2 = strIp.indexOf(".", position1 + 1); int position3 = strIp.indexOf(".", position2 + 1); // 将每个.之间的字符串转换成整型 ip[0] = Long.parseLong(strIp.substring(0, position1)); ip[1] = Long.parseLong(strIp.substring(position1 + 1, position2)); ip[2] = Long.parseLong(strIp.substring(position2 + 1, position3)); ip[3] = Long.parseLong(strIp.substring(position3 + 1)); return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]; } /** * 将十进制整数形式转换成127.0.0.1形式的ip地址 * @param longIp 整数型IP * @return 字符串型IP * */ public static String longToIP(long longIp) { StringBuffer sb = new StringBuffer(""); // 直接右移24位 sb.append(String.valueOf((longIp >>> 24))); sb.append("."); // 将高8位置0,然后右移16位 sb.append(String.valueOf((longIp & 0x00FFFFFF) >>> 16)); sb.append("."); // 将高16位置0,然后右移8位 sb.append(String.valueOf((longIp & 0x0000FFFF) >>> 8)); sb.append("."); // 将高24位置0 sb.append(String.valueOf((longIp & 0x000000FF))); return sb.toString(); } }
六 多线程批量扫描
先将IP转化成long型数据,然后根据线程数量将这一连续的IP段均匀分给每个线程执行,再通过调用ServerTypeDemo.java这个核心类发出Get请求,最后是将获取到的信息写入到文件中
(PS:关于多线程处理IP段的原理不太理解的可以看我写的这篇文章:http://www.zifangsky.cn/2015/12/多线程循环批量处理以及多线程操作文件写入相关/)
MyThread.java:
package action; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import util.IPTraverse; public class MyThread implements Runnable { private long sips; // 起始IP转化的数组 private long eips; // 结束IP转化的数组 private int i; // 第几个线程 private int threadNum; // 总共创建了几个线程 private String fileName; /** * 根据输入的数据多线程批量扫描一个IP段的服务器类型(Apache,tomcat,nginx,IIS。。。) * * @param sips * 起始IP转化的整数 * @param eips * 结束IP转化的整数 * @param i * 这是第几个线程 * @param fileName * 结果所保存的文件名 * @param threadNumber * 扫描的线程数 * * @return null * */ public MyThread(long sips, long eips, int i, int threadNum, String fileName) { this.sips = sips; this.eips = eips; this.i = i; this.threadNum = threadNum; this.fileName = fileName; } public void run() { try { BufferedWriter writer = new BufferedWriter(new FileWriter(new File(fileName),true)); // 遍历每个IP for (long step = sips + i; step <= eips; step = step + threadNum) { String tempIP = IPTraverse.longToIP(step); // System.out.println(tempIP); //这里只扫描了80和8080端口 ServerTypeDemo.savaData(tempIP, "80", writer); ServerTypeDemo.savaData(tempIP, "8080", writer); } writer.close(); } catch (IOException e) { e.printStackTrace(); } } }
七 测试
我随便找了一个IP段进行测试,结果如下:
好了,文章到此结束。
(PS:欢迎大家访问我的个人博客网站:http://www.zifangsky.cn)
附:完整源代码以及打包好的jar包下载:链接: http://down.51cto.com/data/2125949
(PS:如果没有下载豆的话,可以浏览我个人博客网站上的这篇文章,文末有百度云盘的链接,传送门:http://www.zifangsky.cn/2015/12/多线程批量探测目标ip段的服务器类型/ )