2025-02-17

文/圖 海嘯天鳴(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就更強大瞭

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *