一 原理解释
这里所说的服务器类型是指像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”,最后将结果写入文件。代码如下:
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
|
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),线程数,最后点击开始进行扫描,待全部线程结束后会给出提示信息。界面相关代码如下:
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
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();
}
|
五 处理点击事件
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
|
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
,
"邮箱:admin@zifangsky.cn\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:
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
|
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:
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
|
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段进行测试,结果如下:
好了,文章到此结束。
本文转自 pangfc 51CTO博客,原文链接:http://blog.51cto.com/983836259/1722429,如需转载请自行联系原作者