如何简单实现内网穿透(Java)

createh54周前 (04-07)技术教程5

背景

有时我们想在公司远程自己电脑,或者本地部署的模型给外部访问,还有一些微信、QQ的回调测试,我们都是想要外部能够访问得到,这时候你可能需要内网穿透来实现,虽然内网穿透工具有很多了,但是有很多限制,配置的参数又多,为何尝试自己去实现一个。

内网穿透

内网穿透,顾名思义:内网一般是无法访问,直接穿透他,将应用的端口暴露到公网上,通过公网上允许外部访问。

实现流程图

例如,我们在本机上部署了一个应用,为了能让外部的用户能访问到,我们需要一个中介云服务器,本机的应用一启动就去会去云服务器的。当用户访问应用的时候,就会先访问云服务器,这时云服务器就会告诉个人电脑有用户来访问我了,赶紧给我请求连接。个人电脑收到命令,就会连接云服务器,之后在云服务器上会有两个连接:一个是用户访问的和另一个是个人电脑请求的。最后就让这个两个连接交换数据,完成了应用的访问。

准备环境

  • 云服务器
  • 编程语言(Java)

你想要外部访问肯定是要云服务器(公网服务器),当然你可以在本地测试好了,再部署到云服务器上。编程语言我这里就选择Java,当然你可以选择其他语言.Net、Python、Go,相对我来说比较熟悉Java,用.Net也行感觉会简单些。

代码

下面代码使用的是JDK23:AIO+虚拟线程。AIO是非阻塞、异步IO,编程来说相对简单些,但是占得内存会很多,主要是每个连接要创建buffer交换数据。如果使用NIO的话,你会发现一个线程管理多个连接,而且就只用一个buffer交换数据,内存占用会少很多,但是编程起来相对麻烦些。零拷贝技术,减少中间赚差价,本来系统读取数据是分配到系统空间的,Java程序要使用这部数据需要拷贝到自己的虚拟机上转换才能使用,那不是多拷贝了一次,直接使用系统空间的不就行了。

下面代码主要有客户端和服务端,代码还挺简单的。

客户端

public class Client {

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

        //虚拟线程池
        var executor = Executors.newVirtualThreadPerTaskExecutor();

        // 从系统属性获取值,若未设置则使用默认值
        int appPort = Integer.parseInt(System.getProperty("appPort", "3389"));
        int remotePort = Integer.parseInt(System.getProperty("remotePort", "8888"));
        String remoteHost = System.getProperty("remoteHost", "192.168.1.195");


        var client = AsynchronousSocketChannel.open();
        Future connect = client.connect(new InetSocketAddress(remoteHost, remotePort));
        //等待连接
        connect.get();

        System.out.printf("客户端连接成功:appPort:%d,remotePort:%d,remoteHost:%s%n",appPort,remotePort,remoteHost);
        //写入100告诉服务端是用来交换数据的
        var buffer =  ByteBuffer.allocate(4).putInt(100).flip();
        var l = client.write(buffer).get();

        while (!Thread.interrupted()) {
            buffer = ByteBuffer.allocate(1);
            //读取服务端
            var len = client.read(buffer).get();
            if(len < 0 client.close else buffer.flip if buffer.get='= 66)' executor.submit -> {
                        try (
                                //连接远程
                                var remote = AsynchronousSocketChannel.open();
                                //连接本地
                                var local = AsynchronousSocketChannel.open();
                        ) {

                            Future remoteConnect = remote.connect(new InetSocketAddress(remoteHost, remotePort));
                            Future localConnect = local.connect(new InetSocketAddress("localhost", appPort));

                            localConnect.get(3, TimeUnit.SECONDS);
                            remoteConnect.get(1, TimeUnit.SECONDS);
                            System.out.println(localConnect);

                            //告诉服务端是一个连接
                            var bf =  ByteBuffer.allocate(4).putInt(200).flip();
                            remote.write(bf).get(3, TimeUnit.SECONDS);

                            //开始交换数据
                            Future taskFuture1 = executor.submit(() -> {
                                transferData(remote, local);
                            });

                            Future taskFuture2 = executor.submit(() -> {
                                transferData(local, remote);
                            });

                            taskFuture1.get();
                            taskFuture2.get();

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

                    });

                } else {
                    client.close();
                    break;
                }
            }

        }

    }
    private static void transferData(AsynchronousSocketChannel srcChannel, AsynchronousSocketChannel dstChannel) {
        int readLen;
        do{
            try {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                Future read1 = srcChannel.read(buffer);
                readLen = read1.get();
                if(readLen>0){
                    buffer.flip();
                    while (buffer.remaining()>0){
                        Future write1 = dstChannel.write(buffer);
                        write1.get();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }while (readLen>0);
    }
}

服务端

public class Server {

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

        int port = Integer.parseInt(System.getProperty("port", "8888"));
        //开启服务端
        try(var serverChannel = AsynchronousServerSocketChannel.open()){
            serverChannel.bind(new InetSocketAddress("0.0.0.0", port));
            System.out.printf("服务端启动:%d%n",port);
            //客户端连接,用来告知需要连接
            AtomicReference client = new AtomicReference<>(null);
            //客户端那边提供的连接队列
            LinkedBlockingQueue channelQueue = new LinkedBlockingQueue<>();

            //虚拟线程池
            var executor = Executors.newVirtualThreadPerTaskExecutor();

            while (!Thread.interrupted()){
                try {
                    AsynchronousSocketChannel srcChannel = serverChannel.accept().get();
                    System.out.println("有连接:"+srcChannel);

                    // 提交到虚拟线程池
                    executor.submit(() -> {
                        try {
                            var buffer = ByteBuffer.allocate(1024);
                            var len = srcChannel.read(buffer).get();
                            //切换读状态
                            buffer.flip();

                            if(len < 0 srcchannel.close else iflen='= 4){' var code='buffer.getInt();' ifcode='= 100){' ifclient.get client.get.close client.setsrcchannel else if code='= 200){' channelqueue.offersrcchannel else srcchannel.close else ifclient.get='= null){' srcchannel.close else client.get.writebytebuffer.wrapnew byte66.get asynchronoussocketchannel asc='channelQueue.poll(5,' timeunit.seconds ifasc='null){' try ascsrcchannel while buffer.remaining>0){
                                                asc.write(buffer).get();
                                            }

                                            System.out.println(srcChannel);

                                            //开始交换数据
                                            Future taskFuture1 = executor.submit(() -> {
                                                transferData(srcChannel, asc);
                                            });

                                            Future taskFuture2 = executor.submit(() -> {
                                                transferData(asc, srcChannel);
                                            });

                                            taskFuture1.get();
                                            taskFuture2.get();

                                        }catch (Exception e){
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            if(srcChannel!=null){
                                try {
                                    srcChannel.close();
                                } catch (IOException ex) {
                                    ex.printStackTrace();
                                }
                            }
                        }

                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private static void transferData(AsynchronousSocketChannel srcChannel, AsynchronousSocketChannel dstChannel) {
        int readLen;
        do{
            try {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                Future read1 = srcChannel.read(buffer);
                readLen = read1.get();
                if(readLen>0){
                    buffer.flip();
                    while (buffer.remaining()>0){
                        Future write1 = dstChannel.write(buffer);
                        write1.get();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }while (readLen>0);
    }
}

完整项目:netTunnlDemo2: 简单内网穿透实现,JDK23、AIO+虚拟线程

相关文章

面试必备之对象拷贝神器

BeanUtils.copyProperties vs BeanCopier:谁才是Java界的“复制粘贴之王”?1. 开篇:Java界的“复制粘贴”江湖在Java的世界里,对象的复制粘贴(属性拷贝)...

Java虚拟机的GC算法:标记-清除与复制

Java虚拟机的GC算法:标记-清除与复制在Java编程的世界里,垃 圾回收(Garbage Collection, GC)就像是一位默默工作的园丁,它清理着程序运行过程中不再需要的对象,为新生对象腾...

不要网上乱拷贝代码了!一段网上找的代码突然炸了!

作者:陈宏鸿 https://www.cnblogs.com/aspwebchh/p/12220673.html碰到一个需求,给服某些要求的玩家的发送道具奖励,奖励的数量根据离线的天数计算。这个需求实...

正确复制、重写别人的代码,不算抄袭

我最近在一篇文章提到,工程师应该怎样避免使用大量的库、包以及其他依赖关系。我建议的另一种方案是,如果你没有达到重用第三方代码的阈值时,那么你就可以自己编写代码。在本文中,我将讨论一个在重用和从头开始编...

零拷贝技术——让数据传输效率飙升的底层“黑科技”

零拷贝技术——让数据传输效率飙升的底层“黑科技”一、从“快递员拆箱”到零拷贝:数据传输为何需要革命?想象一下,你网购的商品从仓库到你家,要经历仓库→物流中心→快递员→你家。如果每个环节都要拆箱检查再打...