欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Java實現(xiàn)簡單搭建內(nèi)網(wǎng)穿透

 更新時間:2024年02月25日 11:35:20   作者:cloudy491  
內(nèi)網(wǎng)穿透是一種網(wǎng)絡(luò)技術(shù),適用于需要遠程訪問本地部署服務(wù)的場景,本文主要為大家介紹了如何使用Java實現(xiàn)簡單搭建內(nèi)網(wǎng)穿透,感興趣的可以了解下

思路

內(nèi)網(wǎng)穿透是一種網(wǎng)絡(luò)技術(shù),適用于需要遠程訪問本地部署服務(wù)的場景,比如你在家里搭建了一個網(wǎng)站或者想遠程訪問家里的電腦。由于本地部署的設(shè)備使用私有IP地址,無法直接被外部訪問,因此需要通過公網(wǎng)IP實現(xiàn)訪問。通??梢酝ㄟ^購買云服務(wù)器獲取一個公網(wǎng)IP來實現(xiàn)這一目的。

實際上,內(nèi)網(wǎng)穿透的原理是將位于公司或其他工作地點的私有IP數(shù)據(jù)發(fā)送到云服務(wù)器(公網(wǎng)IP),再從云服務(wù)器發(fā)送到家里的設(shè)備(私有IP)。從私有IP到公網(wǎng)IP的連接是相對簡單的,但是從公網(wǎng)IP到私有IP就比較麻煩,因為公網(wǎng)IP無法直接找到私有IP。

為了解決這個問題,我們可以讓私有IP主動連接公網(wǎng)IP。這樣,一旦私有IP連接到了公網(wǎng)IP,公網(wǎng)IP就知道了私有IP的存在,它們之間建立了連接關(guān)系。當公網(wǎng)IP收到訪問請求時,就會通知私有IP有訪問請求,并要求私有IP連接到公網(wǎng)IP。這樣一來,公網(wǎng)IP就建立了兩個連接,一個是用于訪問的連接,另一個是與私有IP之間的連接。最后,通過這兩個連接之間的數(shù)據(jù)交換,實現(xiàn)了遠程訪問本地部署服務(wù)的目的。

代碼操作

打開IDEA創(chuàng)建一個mave項目,刪除掉src,創(chuàng)建兩個模塊clientservice,一個是在本地的運行,一個是在云服務(wù)器上運行的,這邊socket(tcp)連接,我使用的是AIO,AIO的函數(shù)回調(diào)看起來好復(fù)雜。

先編寫service服務(wù)端,創(chuàng)建兩個ServerSocket服務(wù),一個是監(jiān)聽16000的,用來外來連接的,另一是監(jiān)聽16088是用來client訪問的,也就是給serviceclient之間交互用的。先講一個extListener他是監(jiān)聽16000,當有外部請求來時,也就是在公司訪問時,先判斷registerChannel是不是有clientservice,沒有就關(guān)閉連接。有的話就下發(fā)指令告訴client有訪問了趕快給我連接,連接會存在channelQueue隊列里,拿到連接后,兩個連接交換數(shù)據(jù)就行。

private static final int extPort = 16000;
private static final int clintPort = 16088;


private static AsynchronousSocketChannel registerChannel;

static BlockingQueue<AsynchronousSocketChannel> channelQueue = new LinkedBlockingQueue<>();

public static void main(String[] args) throws IOException {

    final AsynchronousServerSocketChannel listener =
            AsynchronousServerSocketChannel.open().bind(new InetSocketAddress("192.168.1.10", clintPort));

    listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
        public void completed(AsynchronousSocketChannel ch, Void att) {

            // 接受連接,準備接收下一個連接
            listener.accept(null, this);

            // 處理連接
            clintHandle(ch);
        }

        public void failed(Throwable exc, Void att) {
            exc.printStackTrace();
        }
    });


    final AsynchronousServerSocketChannel extListener =
            AsynchronousServerSocketChannel.open().bind(new InetSocketAddress("localhost", extPort));

    extListener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {

        private Future<Integer> writeFuture;

        public void completed(AsynchronousSocketChannel ch, Void att) {
            // 接受連接,準備接收下一個連接
            extListener.accept(null, this);

            try {
                //判斷是否有注冊連接
                if(registerChannel==null || !registerChannel.isOpen()){
                    try {
                        ch.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return;
                }
                //下發(fā)指令告訴需要連接
                ByteBuffer bf = ByteBuffer.wrap(new byte[]{1});
                if(writeFuture != null){
                    writeFuture.get();
                }
                writeFuture = registerChannel.write(bf);

                AsynchronousSocketChannel take = channelQueue.take();

                //clint連接失敗的
                if(take == null){
                    ch.close();
                    return;
                }

                //交換數(shù)據(jù)
                exchangeDataHandle(ch,take);

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        public void failed(Throwable exc, Void att) {
            exc.printStackTrace();
        }
    });

    Scanner in = new Scanner(System.in);
    in.nextLine();


}

看看clintHandle方法是怎么存進channelQueue里的,很簡單client發(fā)送0,就認為他是注冊的連接,也就交互的連接直接覆蓋registerChannel,發(fā)送1的話就是用來交換數(shù)據(jù)的,扔到channelQueue,發(fā)送2就異常的連接。

private static void clintHandle(AsynchronousSocketChannel ch) {

    final ByteBuffer buffer = ByteBuffer.allocate(1);
    ch.read(buffer, null, new CompletionHandler<Integer, Void>() {
        public void completed(Integer result, Void attachment) {
            buffer.flip();
            byte b = buffer.get();
            if (b == 0) {
                registerChannel = ch;
            } else if(b == 1){
                channelQueue.offer(ch);
            }else{
                //clint連接不到
                channelQueue.add(null);
            }

        }

        public void failed(Throwable exc, Void attachment) {
            exc.printStackTrace();
        }
    });
}

再編寫client客戶端,dstHostdstPort是用來連接service的ip和端口,看起來好長,實際上就是client連接service,第一個連接成功后向service發(fā)送了個0告訴他是注冊的連接,用來交換數(shù)據(jù)。當這個連接收到service發(fā)送的1時,就會創(chuàng)建新的連接去連接service。

private static final String dstHost = "192.168.1.10";
private static final int dstPort = 16088;

private static final String srcHost = "localhost";
private static final int srcPort = 3389;


public static void main(String[] args) throws IOException {

    System.out.println("dst:"+dstHost+":"+dstPort);
    System.out.println("src:"+srcHost+":"+srcPort);

    //使用aio
    final AsynchronousSocketChannel client = AsynchronousSocketChannel.open();

    client.connect(new InetSocketAddress(dstHost, dstPort), null, new CompletionHandler<Void, Void>() {
        public void completed(Void result, Void attachment) {
            //連接成功
            byte[] bt = new byte[]{0};
            final ByteBuffer buffer = ByteBuffer.wrap(bt);
            client.write(buffer, null, new CompletionHandler<Integer, Void>() {
                public void completed(Integer result, Void attachment) {

                    //讀取數(shù)據(jù)
                    final ByteBuffer buffer = ByteBuffer.allocate(1);
                    client.read(buffer, null, new CompletionHandler<Integer, Void>() {
                        public void completed(Integer result, Void attachment) {
                            buffer.flip();

                            if (buffer.get() == 1) {
                                //發(fā)起新的連
                                try {
                                    createNewClient();
                                } catch (IOException e) {
                                    throw new RuntimeException(e);
                                }
                            }
                            buffer.clear();
                            // 這里再次調(diào)用讀取操作,實現(xiàn)循環(huán)讀取
                            client.read(buffer, null, this);
                        }

                        public void failed(Throwable exc, Void attachment) {
                            exc.printStackTrace();
                        }
                    });


                }

                public void failed(Throwable exc, Void attachment) {
                    exc.printStackTrace();
                }
            });


        }

        public void failed(Throwable exc, Void attachment) {
            exc.printStackTrace();
        }
    });
    Scanner in = new Scanner(System.in);
    in.nextLine();

}

createNewClient方法,嘗試連接本地服務(wù),如果失敗就發(fā)送2,成功就發(fā)送1,這個會走 serviceclintHandle方法,成功的話就會讓兩個連接交換數(shù)據(jù)。

private static void createNewClient() throws IOException {

    final AsynchronousSocketChannel dstClient = AsynchronousSocketChannel.open();
    dstClient.connect(new InetSocketAddress(dstHost, dstPort), null, new CompletionHandler<Void, Void>() {
        public void completed(Void result, Void attachment) {

            //嘗試連接本地服務(wù)
            final AsynchronousSocketChannel srcClient;
            try {
                srcClient = AsynchronousSocketChannel.open();
                srcClient.connect(new InetSocketAddress(srcHost, srcPort), null, new CompletionHandler<Void, Void>() {
                    public void completed(Void result, Void attachment) {

                        byte[] bt = new byte[]{1};
                        final ByteBuffer buffer = ByteBuffer.wrap(bt);
                        Future<Integer> write = dstClient.write(buffer);
                        try {
                            write.get();
                            //交換數(shù)據(jù)
                            exchangeData(srcClient, dstClient);
                            exchangeData(dstClient, srcClient);
                        } catch (Exception e) {
                            closeChannels(srcClient, dstClient);
                        }


                    }

                    public void failed(Throwable exc, Void attachment) {
                        exc.printStackTrace();
                        //失敗
                        byte[] bt = new byte[]{2};
                        final ByteBuffer buffer = ByteBuffer.wrap(bt);
                        dstClient.write(buffer);
                    }
                });

            } catch (IOException e) {
                e.printStackTrace();
                //失敗
                byte[] bt = new byte[]{2};
                final ByteBuffer buffer = ByteBuffer.wrap(bt);
                dstClient.write(buffer);
            }

        }

        public void failed(Throwable exc, Void attachment) {
            exc.printStackTrace();
        }
    });
}

下面是exchangeData交換數(shù)據(jù)方法,看起好麻煩,效果就類似IOUtils.copy(InputStream,OutputStream),一個流寫入另一個流。

private static void exchangeData(AsynchronousSocketChannel ch1, AsynchronousSocketChannel ch2) {
    try {
        final ByteBuffer buffer = ByteBuffer.allocate(1024);

        ch1.read(buffer, null, new CompletionHandler<Integer, CompletableFuture<Integer>>() {

            public void completed(Integer result, CompletableFuture<Integer> readAtt) {

                CompletableFuture<Integer> future = new CompletableFuture<>();

                if (result == -1 || buffer.position() == 0) {
                    // 處理連接關(guān)閉的情況或者沒有數(shù)據(jù)可讀的情況

                    try {
                        readAtt.get(3,TimeUnit.SECONDS);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    closeChannels(ch1, ch2);
                    return;
                }

                buffer.flip();

                CompletionHandler readHandler = this;

                ch2.write(buffer, future, new CompletionHandler<Integer, CompletableFuture<Integer>>() {
                    @Override
                    public void completed(Integer result, CompletableFuture<Integer> writeAtt) {

                        if (buffer.hasRemaining()) {
                            // 如果未完全寫入,則繼續(xù)寫入
                            ch2.write(buffer, writeAtt, this);

                        } else {
                            writeAtt.complete(1);
                            // 清空buffer并繼續(xù)讀取
                            buffer.clear();
                            if(ch1.isOpen()){
                                ch1.read(buffer, writeAtt, readHandler);
                            }
                        }

                    }

                    @Override
                    public void failed(Throwable exc, CompletableFuture<Integer> attachment) {
                        if(!(exc instanceof AsynchronousCloseException)){
                            exc.printStackTrace();
                        }
                        closeChannels(ch1, ch2);
                    }
                });

            }

            public void failed(Throwable exc, CompletableFuture<Integer>  attachment) {
                if(!(exc instanceof AsynchronousCloseException)){
                    exc.printStackTrace();
                }
                closeChannels(ch1, ch2);
            }
        });

    } catch (Exception ex) {
        ex.printStackTrace();
        closeChannels(ch1, ch2);
    }

}

private static void closeChannels(AsynchronousSocketChannel ch1, AsynchronousSocketChannel ch2) {
    if (ch1 != null && ch1.isOpen()) {
        try {
            ch1.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if (ch2 != null && ch2.isOpen()) {
        try {
            ch2.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

測試

我這邊就用虛擬機來測試,用云服務(wù)器就比較麻煩,得登錄賬號,增加開放端口規(guī)則,上傳代碼。我這邊用Hyper-V快速創(chuàng)建了虛擬機,創(chuàng)建一個windows 10 MSIX系統(tǒng),安裝JDK8,下載地址 。怎樣把本地編譯好的class放到虛擬機呢,虛擬機是可以訪問主機ip的,我們可以弄一個web的文件目錄下載給虛擬機訪問,人生苦短我用pyhton,下面python簡單代碼

if __name__ == '__main__':
    # 定義服務(wù)器的端口
    PORT = 8000

    # 創(chuàng)建請求處理程序
    Handler = http.server.SimpleHTTPRequestHandler

    # 設(shè)置工作目錄
    os.chdir("C:\netTunnlDemo\client\target")

    # 創(chuàng)建服務(wù)器
    with socketserver.TCPServer(("", PORT), Handler) as httpd:
        print(f"服務(wù)啟動在端口 {PORT}")
        httpd.serve_forever()

到class的目錄下運行cmd,執(zhí)行java -cp . org.example.Main,windows 默認遠程端口3389。

最后效果

以上就是使用Java實現(xiàn)簡單搭建內(nèi)網(wǎng)穿透的詳細內(nèi)容,更多關(guān)于Java內(nèi)網(wǎng)穿透的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • mybatis?@InsertProvider報錯問題及解決

    mybatis?@InsertProvider報錯問題及解決

    這篇文章主要介紹了mybatis?@InsertProvider報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • FreeSWITCH跨NAT部署配置詳解

    FreeSWITCH跨NAT部署配置詳解

    這篇文章主要為大家介紹了FreeSWITCH跨NAT部署配置詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • 聊聊如何在springboot中添加模版

    聊聊如何在springboot中添加模版

    本文,我們談?wù)勅绾卧?nbsp;spring boot 中添加模版,因為有時候我們也是需要后端渲染的嘛,比如公司官網(wǎng),文中有詳細的代碼示例供我們參考,需要的朋友可以參考下
    2023-08-08
  • java 中http請求為了防止亂碼解決方案

    java 中http請求為了防止亂碼解決方案

    這篇文章主要介紹了java 中http請求為了防止亂碼解決方案的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • java 出現(xiàn)問題javax.servlet.http.HttpServlet was not found解決方法

    java 出現(xiàn)問題javax.servlet.http.HttpServlet was not found解決方法

    這篇文章主要介紹了java 出現(xiàn)問題javax.servlet.http.HttpServlet was not found解決方法的相關(guān)資料,需要的朋友可以參考下
    2016-11-11
  • 深入分析java并發(fā)編程中volatile的實現(xiàn)原理

    深入分析java并發(fā)編程中volatile的實現(xiàn)原理

    這篇文章主要介紹了深入分析java并發(fā)編程中Volatile的實現(xiàn)原理,涉及Volatile的官方定義,實現(xiàn)原理,使用優(yōu)化等相關(guān)內(nèi)容,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • 微信支付H5調(diào)用支付詳解(java版)

    微信支付H5調(diào)用支付詳解(java版)

    本篇文章主要介紹了微信支付H5調(diào)用支付詳解,小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧。
    2016-12-12
  • Spring MVC的項目準備和連接建立方法

    Spring MVC的項目準備和連接建立方法

    SpringWebMVC是基于Servlet API的Web框架,屬于Spring框架的一部分,主要用于簡化Web應(yīng)用程序的開發(fā),SpringMVC通過控制器接收請求,使用模型處理數(shù)據(jù),并通過視圖展示結(jié)果,感興趣的朋友跟隨小編一起看看吧
    2024-10-10
  • java數(shù)組基礎(chǔ)詳解

    java數(shù)組基礎(chǔ)詳解

    下面小編就為大家?guī)硪黄狫ava創(chuàng)建數(shù)組的幾種方式總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望能給大家?guī)韼椭?/div> 2021-06-06
  • SpringBoot資源文件的存放位置設(shè)置方式

    SpringBoot資源文件的存放位置設(shè)置方式

    這篇文章主要介紹了SpringBoot資源文件的存放位置設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評論