目錄
一、埠掃描技術
二、 單執行緒建立 Socket 連接
三、 基于 TCP Connect 埠掃描
四、 多執行緒 TCP 連接掃描
五、 小程式完整原始碼
一、埠掃描技術
我們都知道,網路中的每臺機器都有IP地址,與IP地址密切相關的就是主機的埠,順便考考你,知道埠號的范圍嗎?
沒錯,就是0~65535,在計算機網路中,使用兩個位元組供16位二進制數來表示埠號,對于這些埠號,一臺主機它有開放哪些埠號呢?
比如常見的80,443兩個埠號對應的就是HTTP,HTTPS服務,23 埠對應telnet遠程管理,25 埠是SMTP服務等等,下面我們就來實作一個簡易的埠掃描小程式,
掃描主機開放的埠是常見操作:攻(尋找目的主機開放的埠)與防(檢測本機例外開放的埠),基本的埠掃描可使用的技術:
- 創建Socket連接:new Socket(ip,port)
- TCP Connect 探測
本篇適用計算機網路、Java開發、Socket編程以及多執行緒入門的伙伴!可以結合上一篇《Java開發主機IP掃描神器》,進一步探測網路攻防有用資訊!
二、 單執行緒建立 Socket 連接
現在開始小程式的簡易開發流程,從單執行緒開始一步一步前進,
Socket socket=new Socket(host,port);
這里,我們通過創建一個Socket連接,這種方式原理就是與目標IP建立連接,監聽埠,如果超過一定時間,說明該埠沒有開放,無法成功連接,
無法連接的情況會拋出 IOException 例外,因此,可以在拋出例外的時候判定埠是關閉狀態,
具體地實作很簡單,我們把掃描埠的操作放到一個執行緒里面,不影響主程式的運行,然后對給出的埠范圍比如 0到100,逐個進行Socket連接,這樣就可以知道哪些埠的open的,
try {
Socket socket=new Socket(host,port);
socket.close();
Platform.runLater(() -> {
result.appendText("埠 " +port+ " is open.\n");
});
} catch (IOException e) {
result.appendText("埠 " +port+ " is closed.\n");
}

這是對我的主機部分埠掃描的結果,
下面是動圖可以看一下程序,

三、 基于 TCP Connect 埠掃描
從動態圖的掃描程序可以發現,這個速度明顯不是人可以容忍的,就像一個網頁加載半天一直沒顯示出來一樣,所以,為了更好的用戶體驗,在此基礎上進行優化改進,最重要的就是掃描速度!
上一個方法在遇到埠關閉時等待時間過長,時間成本過高,開始使用另一個方法,基于TCP Connect 的埠掃描,
Socket socket=new Socket();
socket.connect(new InetSocketAddress(host,port),200);
socket.close();
這時候,不會對每個ip和埠真正建立連接,而是使用“探測”的方式,通過InetSocketAddress類進行連接,超時時間設定200ms,明顯這樣速度至少可以提高幾倍!
代碼實作很簡單,這樣寫就搞定啦
try {
Socket socket=new Socket();
socket.connect(new InetSocketAddress(host,port),200);
socket.close();
Platform.runLater(() -> {
result.appendText("埠 " +port+ " is open.\n");
});
} catch (IOException e) {
result.appendText("埠 " +port+ " is closed.\n");
}
優化改進之后的對比掃描速度如下動圖:

對比之下,掃描速度大幅度提升!
這時,速度的提升是相對而言,比如掃描1000個埠,你就知道有多慢了,還是等不及!

20秒竟然才掃描不到80個埠!粗略計算,1000個埠至少需要4分鐘,這是無法接受的!
四、 多執行緒 TCP 連接掃描
這時候,只能使用后手了,多執行緒專場…… 嘿嘿
我們現在的小目標就是速度要快,多執行緒操作真不賴,這里我開啟100個執行緒,來完成這項掃描的任務,
for (int i=0;i<100;i++) {
readThread=new Thread(new ScanHandler(i, 100),"scanThread");
readThread.start();
}
同時,開啟多執行緒就需要注意執行緒安全問題,在這個任務,還需要關注每個執行緒負責的埠范圍,避免重復掃描以及掃描結束判斷,
使用原子變數來保證執行緒安全,計數正確解決上述問題,
static AtomicInteger portCount portCount=new AtomicInteger(0);
定義一個掃描執行緒的處理類ScanHandler,處理邏輯大概就是:每個執行緒有自己的標識號,根據標識號確定自己負責的埠號,每個執行緒都維護著已掃描的埠原子計數,
class ScanHandler implements Runnable{
private int totalThreadNum;//用于埠掃描的總共執行緒數量,默認為10
private int threadNo;//執行緒號,表示第幾個執行緒
private int startP=Integer.parseInt(startPort.getText());
private int endP=Integer.parseInt(endPort.getText());
private String host = targetIP.getText().trim();
public ScanHandler(int threadNo) {
this.totalThreadNum = 10;
this.threadNo = threadNo;
}
public ScanHandler(int threadNo,int totalThreadNum) {
this.totalThreadNum = totalThreadNum;
this.threadNo = threadNo;
}
@Override
public void run() {
//startPort和endPort為成員變數,表示需要掃描的起止埠
for (int i=startP+threadNo;i<=endP;i=i+totalThreadNum){
int port=i;
if (readThread.isInterrupted()){
readThread.interrupt();
break;
}
try {
Socket socket=new Socket();
socket.connect(new InetSocketAddress(host,port),200);
socket.close();
Platform.runLater(() -> {
result.appendText("埠 " +port+ " is open.\n");
});
}catch (IOException e){
// result.appendText("埠 " +i+ " is closed.\n");
}
portCount.incrementAndGet();
}
if (portCount.get()==(endP-startP+1)){//判斷掃描結束
portCount.incrementAndGet();
Platform.runLater(()->{
result.appendText("\n-------------多執行緒掃描結束-------------\n");
});
}
}
}
再來看看多執行緒操作的速度有多快,現在感覺很順暢,1000個埠不到10秒就完成掃描!

是不是感覺挺完美了,但根據常識總是差點什么,其實就是一個進度條顯示,就像我們下載東西一樣,有個進度條和百分比看著心里有底,安排!
Java中就有可以直接使用的進度類,就是下面這行代碼,然后再加到掃描執行緒里面,實時更新進度,

ProgressBar progressBar=new ProgressBar();
來看看效果,用多執行緒版本掃描5000個埠,

五、 小程式完整原始碼
完整原始碼,毫無保留,建議果斷收藏,以免以后使用找不到!
/**
* HostScannerFX.java
* Copyright (c) 2021 Charzous
* All right reserved.
* @date 2021-06-07 下午 09:38
*/
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicInteger;
public class PortScannerFX extends Application {
private TextArea result = new TextArea();
private TextField targetIP = new TextField();
private TextField startPort = new TextField();
private TextField endPort = new TextField();
private Button scan = new Button("掃描");
private Button quickScan = new Button("快速掃描");
private Button threadScan = new Button("多執行緒掃描");
private Button ex=new Button("退出");
private Button stop=new Button("停止掃描");
private Thread readThread;
static AtomicInteger portCount;//用于統計已掃描的埠數量
private ProgressBar progressBar=new ProgressBar();
private Label bar=new Label("0%");
@Override
public void start(Stage primaryStage) throws Exception {
BorderPane mainPane = new BorderPane();
HBox barBox=new HBox();
barBox.setSpacing(10);
barBox.setPadding(new Insets(10, 0, 10, 0));
progressBar.setPrefWidth(700);
progressBar.setProgress(0);
HBox.setHgrow(progressBar,Priority.ALWAYS);
barBox.getChildren().addAll(bar,progressBar);
VBox vBox = new VBox();
vBox.setSpacing(10);
vBox.setPadding(new Insets(10, 20, 10, 20));
// vBox.setAlignment(Pos.CENTER);
VBox.setVgrow(result, Priority.ALWAYS);
vBox.getChildren().addAll(new Label("埠掃描結果:"), result,barBox);
mainPane.setCenter(vBox);
startPort.setPrefWidth(60);
endPort.setPrefWidth(60);
HBox hBox1 = new HBox();
hBox1.setSpacing(10);
hBox1.setPadding(new Insets(10, 20, 10, 20));
hBox1.setAlignment(Pos.CENTER);
hBox1.getChildren().addAll(new Label("目標主機ip:"), targetIP, new Label("起始埠號:"), startPort, new Label("結束埠號:"),endPort);
HBox hBox2 = new HBox();
hBox2.setSpacing(10);
hBox2.setPadding(new Insets(10, 20, 10, 20));
hBox2.setAlignment(Pos.CENTER);
hBox2.getChildren().addAll(scan,quickScan,threadScan,stop,ex);
VBox vBox1 = new VBox();
vBox1.setSpacing(10);
vBox1.setPadding(new Insets(10, 20, 10, 20));
vBox1.setAlignment(Pos.CENTER);
vBox1.getChildren().addAll(hBox1, hBox2);
mainPane.setBottom(vBox1);
Scene scene = new Scene(mainPane, 800, 500);
primaryStage.setScene(scene);
primaryStage.setTitle("PortScannerFX");
primaryStage.show();
//掃描
scan.setOnAction(event -> {
String host = targetIP.getText().trim();
int sp = Integer.parseInt(startPort.getText());
int ep=Integer.parseInt(endPort.getText());
readThread = new Thread(() -> {
double num=0.0;
for (int i = sp; i <= ep; i++) {
int port=i;
if (readThread.isInterrupted()){
readThread.interrupt();
// Thread.interrupted();
break;
}
try {
Socket socket=new Socket(host,port);
socket.close();
Platform.runLater(() -> {
result.appendText("埠 " +port+ " is open.\n");
});
} catch (IOException e) {
result.appendText("埠 " +port+ " is closed.\n");
}
num++;
double finalNum = num;
Platform.runLater(()->{
progressBar.setProgress(finalNum/(ep-sp+1));//進度條
bar.setText(""+ Integer.valueOf((int) (finalNum /(ep-sp+1)*100))+"%");
});
}
result.appendText("埠掃描結束!\n");
},"scanThread");
readThread.start();
});
//快速掃描
quickScan.setOnAction(event -> {
String host = targetIP.getText().trim();
int sp = Integer.parseInt(startPort.getText());
int ep=Integer.parseInt(endPort.getText());
readThread = new Thread(() -> {
double num=0;
for (int i = sp; i <= ep; i++) {
if (readThread.isInterrupted()){
readThread.interrupt();
// Thread.interrupted();
break;
}
int port=i;
try {
Socket socket=new Socket();
socket.connect(new InetSocketAddress(host,port),200);
socket.close();
Platform.runLater(() -> {
result.appendText("埠 " +port+ " is open.\n");
});
} catch (IOException e) {
result.appendText("埠 " +port+ " is closed.\n");
}
num++;
double finalNum = num;
Platform.runLater(()->{
progressBar.setProgress(finalNum/(ep-sp+1));//進度條
bar.setText(""+ Integer.valueOf((int) (finalNum /(ep-sp+1)*100))+"%");
});
}
result.appendText("埠掃描結束!\n");
},"scanThread");
readThread.start();
});
threadScan.setOnAction(event -> {
portCount=new AtomicInteger(0);
int sp = Integer.parseInt(startPort.getText());
int ep=Integer.parseInt(endPort.getText());
for (int i=0;i<100;i++) {
readThread=new Thread(new ScanHandler(i, 100),"scanThread");
readThread.start();
}
});
stop.setOnAction(event -> {
interrupt("scanThread");
});
//退出
ex.setOnAction(event -> {
exit();
});
primaryStage.setOnCloseRequest(event -> {
exit();
});
}
public void interrupt(String threadName){
ThreadGroup currentGroup=Thread.currentThread().getThreadGroup();
int noThreads=currentGroup.activeCount();
Thread[] lstThreads=new Thread[noThreads];
currentGroup.enumerate(lstThreads);
for (int i=0;i<noThreads;i++){
if (lstThreads[i].getName().equals(threadName))
lstThreads[i].interrupt();
}
}
public void exit(){
System.exit(0);
}
class ScanHandler implements Runnable{
private int totalThreadNum;//用于埠掃描的總共執行緒數量,默認為10
private int threadNo;//執行緒號,表示第幾個執行緒
private int startP=Integer.parseInt(startPort.getText());
private int endP=Integer.parseInt(endPort.getText());
private String host = targetIP.getText().trim();
public ScanHandler(int threadNo) {
this.totalThreadNum = 10;
this.threadNo = threadNo;
}
public ScanHandler(int threadNo,int totalThreadNum) {
this.totalThreadNum = totalThreadNum;
this.threadNo = threadNo;
}
@Override
public void run() {
//startPort和endPort為成員變數,表示需要掃描的起止埠
for (int i=startP+threadNo;i<=endP;i=i+totalThreadNum){
int port=i;
if (readThread.isInterrupted()){
readThread.interrupt();
break;
}
try {
Socket socket=new Socket();
socket.connect(new InetSocketAddress(host,port),200);
socket.close();
Platform.runLater(() -> {
result.appendText("埠 " +port+ " is open.\n");
});
}catch (IOException e){
// result.appendText("埠 " +i+ " is closed.\n");
}
portCount.incrementAndGet();
Platform.runLater(()->{
bar.setText(""+ Integer.valueOf((int) ((portCount.doubleValue())/(endP-startP+1)*100))+"%");//進度比
progressBar.setProgress((portCount.doubleValue())/(endP-startP+1));//進度條
});
}
if (portCount.get()==(endP-startP+1)){//判斷掃描結束
portCount.incrementAndGet();
Platform.runLater(()->{
result.appendText("\n-------------多執行緒掃描結束-------------\n");
});
}
}
}
public static void main(String[] args) {
launch();
}
}
完整原始碼,毫無保留,建議果斷收藏,以免以后使用找不到,趕緊自己動手開發一個簡易埠掃描小程式!
今天用Java開發技術:Socket編程埠掃描小程式,零基礎Socket編程詳細教程,這篇內容是不是簡單、有趣、有識訓呢?歡迎交流學習!
最后想跟大家說的是,學習Java必備的知識有哪些呢?很多粉絲私信我Java學習的路線,我推薦了這套知識圖譜,粉絲們都覺得質量很不錯!
學習Java開發,Socket網路編程等知識,里面有許多有趣的小程式可以做,最近我也在跟著這一套 《Java 工程師學習成長知識圖譜》進行體系的學習,是CSDN官方推出的,質量很不錯!
其中包含了Java專業體系結構完整詳細,推薦給大家學習使用,有興趣可以掃碼查看,最近我也在學習當中,當然,我的文章會記錄學習,歡迎大家閱讀,比如我的專欄《Socket網路編程》、《Java寶藏》,

展開就是這樣的,尺寸870mm*560mm排版好看,內容很充實,推薦給有需要的伙伴,一起來學習Java開發!

如果覺得不錯歡迎“一鍵三連”哦,點贊收藏關注,評論提問建議,歡迎交流學習!一起加油進步,我們下篇見!
本篇內容首發我的CSDN博客:https://csdn-czh.blog.csdn.net/article/details/117672622
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/286278.html
標籤:java
