利用Java实现端口扫描器
文/图 海啸天鸣(Ansty)
上次我们用Java写了一个“文件最后修改时间编辑器”的小黑软,现在我们实现用Java写端口扫描器。为了方便和避免GUI编程的麻烦,我们就直接做成命令行下的工具,用参数来启动它,姑且把它命名为“Java版简单端口扫描工具”。因为本文只是提供Java写黑软的思路,许多算法优化和功能附加不在本文的讨论之列,使用的也是单线程。程序界面如图1所示。
图1
我们知道,利用java.net.Socket类建立socket连接,如果无法与指定的IP和端口建立连接,将会抛出IOException。我们用try-catch对这个IOException异常进行捕获,以判断是否成功与指定的IP端口建立连接。如果成功建立了连接,说明指定IP的指定端口已经开放;如果程序抛出了一个IOException异常被我们捕获,则说明指定的IP没有开放指定的端口。扫描指定端口段则是利用循环不断与服务器的指定端口进行连接,供我们判断是否开放。
我一直坚信,世界上的所有问题只要有了明确的算法,就一定能用程序语言来实现它,无论什么语言!现在,我们有了原理就等于有了算法,你说我们除了技术以外还缺什么?只缺动手了!
因为我们要从程序启动的参数中获得服务器地址、起始端口和终止端口的信息,所以我们就要用到下面的这段代码。
ip = args[0]; //获得我们指定的服务器地址
startPort = Integer.parseInt(args[1]);
//获得起始端口号,因为args[]是String类型,所以要强制转换成int类型
endPort = Integer.parseInt(args[2]);
//获得终止端口号,同上
在得到端口和建立socket之前一定要判断端口的合法性,因为端口的范围是在1~65535,如果我们去建立范围外端口的连接就是没必要的,而且是不可行的。既然是起始端口和终止端口,那么就要有个大小顺序问题,也就是判断它们的大小。
if(startPort<1||startPort>65535||endPort<1||endPort>65535){
//检查端口是否在合法范围1~65535
System.out.printf("端口范围必须在1~65535以内!");
return;
}else if(startPort>endPort){ //比较起始端口和终止端口
System.out.println("端口输入有误!
起始端口必须小于终止端口");
return;
}
建立与服务器指定端口的连接,就要用到java.net.Socket类了,首先我们来看看它的构造方法。
Socket():通过系统默认类型的 SocketImpl 创建未连接套接字。
Socket(InetAddress address, int port):创建一个流套接字,并将其连接到指定 IP 地址的指定端口号。
Socket(InetAddress address, int port, InetAddress localAddr, int localPort):创建一个套接字,并将其连接到指定远程端口上的指定远程地址。
Socket(Proxy proxy):根据不管其他设置如何都应使用的指定代理类型(如果有),创建一个未连接的套接字。
Socket(SocketImpl impl):创建带有用户指定的 SocketImpl 的未连接Socket。
Socket(String host, int port):创建一个流套接字,并将其连接到指定主机上的指定端口号。
Socket(String host, int port, InetAddress localAddr, int localPort):创建一个套接字并将其连接到指定远程主机上的指定远程端口。
可以看到我们有很多种构造方法,目前只需要关心第二种构造方法Socket(InetAddress address, int port)即可,因为我们并不需要与服务器运行在端口的服务进行交互,所以我们只要建立连接,然后关闭连接即可。即:“Socket s = new Socket(address,port);”。
我们在建立连接之前,首先要把IP转换成InetAddress类型,不是说String类型不能用,只是为了减少出现更多异常的可能。
“static InetAddress getByName(String host)”用于在给定主机名的情况下确定主机的 IP 地址。这是静态方法,我们直接InetAddress.getByName()就行了。
try{
InetAddress address = InetAddress.getByName(ip);
//转换类型
}catch(UnknownHostException e){
System.out.println("无法找到 "+ ip);
return;
}
下面就是我们的核心算法了。循环指定端口段的所有端口,对所有端口建立连接。连接成功后我们就算完成了当前循环的任务,然后调用close()方法关闭连接。因为在“Socket s = new Socket(address,nport)”执行的时候,如果成功建立连接,就不会执行到catch里面,而是执行到下面的“result.add(“”+nport)”语句;如果不能连接上去就会抛出一个异常被我们捕获,程序就会运行到catch里面,执行catch里面的语句;最后继续下一个循环。
for(int nport=startPort;nport<=endPort;nport++){
//从起始端口到终止端口进行循环
try{
System.out.print("Scanning "+nport); //打印扫描进度
Socket s=new Socket(address,nport); //建立连接
s.close(); //关闭连接
result.add(""+nport);
//将打开的端口添加到ArrayList result里面
System.out.println(" : open"); //打印状态
}catch(IOException e){
System.out.println(":close"); //打印状态
}
}
在最后打印结果时,我们用ArrayList来存储扫描结果。因为Java里面没有C语言意义上的指针,所以我们在访问ArrayList里面的元素时要用到ListIterator。
ListIterator li = result.listIterator();
//获得ArrayList的ListIterator
while(li.hasNext()){ //如果li里面有元素
System.out.println(li.next().toString()+" Open");
//打印出指向的元素,同时将指向下一个元素
}
好了,现在我们就已经把主要功能的程序代码介绍完了,相信读者看完以后也能用Java编写自己的Java版黑软了。正如我前面所说的,本文只提供一种思路。如果大家有兴趣,可以自己在本文的基础上实现多线程,扩展一些有用的功能,把GUI界面做出来,或者做成仿SuperScan就更强大了
补充:软件开发 , Java ,