惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
Cloudbric
Cloudbric
C
CERT Recently Published Vulnerability Notes
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
C
Cisco Blogs
T
Tenable Blog
P
Privacy International News Feed
T
The Exploit Database - CXSecurity.com
I
Intezer
AWS News Blog
AWS News Blog
IT之家
IT之家
博客园 - 司徒正美
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Spread Privacy
Spread Privacy
S
SegmentFault 最新的问题
博客园 - Franky
人人都是产品经理
人人都是产品经理
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
V
Visual Studio Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
H
Hacker News: Front Page
Latest news
Latest news
Scott Helme
Scott Helme
腾讯CDC
宝玉的分享
宝玉的分享
大猫的无限游戏
大猫的无限游戏
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
Arctic Wolf
S
Securelist
雷峰网
雷峰网
The GitHub Blog
The GitHub Blog
Project Zero
Project Zero
Google DeepMind News
Google DeepMind News
P
Palo Alto Networks Blog
F
Fortinet All Blogs
Schneier on Security
Schneier on Security
云风的 BLOG
云风的 BLOG
Security Archives - TechRepublic
Security Archives - TechRepublic
The Last Watchdog
The Last Watchdog
WordPress大学
WordPress大学
MongoDB | Blog
MongoDB | Blog
L
LINUX DO - 最新话题
S
Schneier on Security
NISL@THU
NISL@THU
Jina AI
Jina AI
M
MIT News - Artificial intelligence

Java

讲讲我怎么获得 kkRepo 的首个企业用户,成功替换掉 Nexus,每年节省 $1,620 PRO 订阅费用 - V2EX Sonatype 正引入 Maven Central 仓库发布使用情况可见性和针对高流量发布活动的限制 - V2EX 让 Java 再次伟大,没有人比我更懂得如何打包!(分享一个四两拨千斤的多 jar 打包 exe 方式) - V2EX 使用 kkRepo 搭建 Maven 私服 - V2EX Nexus 的平替 neuxs-plus 开源涉嫌商标侵权,被迫改名 kkRepo - V2EX 我在 IDEA tab 里养了一只猫 - V2EX Java 确实是内存高效的 用 AI 把 visualvm 的部分功能界面换了个马甲 - V2EX javaer 现在出去面试 问哪些内容 sa-token + spring cloud gateway 过滤器顺序问题 - V2EX 关于在 wsl 中使用 idea 进行 Java 开发的问题 - V2EX 求助, Fortify 漏扫问题解决 - V2EX 微信 ClawBot 和企微 AIBot 的 Java SDK - V2EX 老业务信创重构选型大家有啥建议 - V2EX ai 编程的情况在你们使用什么 IDE - V2EX 下面这段 Java 多态的代码,如何才能改为 Go、 Python 、JS、C++版本? - V2EX JDK 26 发布,非 lts 版本 - V2EX DoytoQuery vs SpringDataJPA - V2EX 求推荐比较优秀的 springboot 脚手架项目 - V2EX vscode 里自用 Java 开发插件:支持复制引用路径、rest 接口路径、Arthas 命令等功能 - V2EX 自荐 Java 多线程神器——ThreadForge ,让多线程从此简单 - V2EX 屎山老项目想用 ai 实现自动化测试,有什么可落地的方案么 - V2EX WebFlux 项目 被访问后 访问方关闭连接 无法捕获 SocketException: Connection reset - V2EX 有人用 RUOYI 框架吗? - V2EX 关于商用 jdk 收费的问题,有遥测吗? - V2EX IDEA 在 macOS 上卡住 - V2EX Java : 2026 年 SpringBoot, Quarkus, Solon ? - V2EX 佬们有没有比较好的 Java 脚手架 - V2EX 一个对比 JavaORM 框架的小仓库 - V2EX 想问一个关于 IO 流的细节 - V2EX 关于我在腾讯云 windows 服务器起了一个后端服务,但是服务器本地都请求不到的问题 11 - V2EX 分享一个 idea 的无响应 bug,如果你也遇到 - V2EX JNI 是否有向"简洁"演化的可能? - V2EX 多实例 springboot 微服务重启时 Counter 类型指标重置问题 - V2EX 讨论个问题:该不该重写 equal 和 hashcode - V2EX 求助 泛型转换碰到一个很奇怪的问题 Object 放 Redis 自动变成 JSONObject 了 - V2EX Java Agent 该怎么快速学习?有教程文档推一下吗? - V2EX 能不能别用那烦人的 MyBatis-Plus 了! - V2EX 作为 Java boy 你们业余时间会学点什么? - V2EX 各位 v 友们!有无无敌好用的剪辑软件求推荐! - V2EX 如何从反射的 Field/Method 动态构造 mybatis 的 SFunction? - V2EX Java 得冗余与啰嗦是不是对 AI 不友好 - V2EX 问一个日志不打印栈信息的问题 - V2EX 为何 RestTemplate 无法定制请求 - V2EX 日经吐槽:现在的 Java 语言真的有点令人恶心 - V2EX 新手 javaer 咨询几个问题 - V2EX 自学 Java 看不懂报错信息和代码,有什么推荐 ai 拿来自学的吗? - V2EX mysql 慢日志分析排查小工具 1.0.0 - V2EX Preferences.userNodeForPackage(Object.class)存储位置在哪里? - V2EX vscode 中 augment code 怎么使用 mcp - V2EX mysql 慢日志分析排查小工具 - V2EX Java 的一次编译到处运行在目前还有优势吗 - V2EX 现在学 Java 算 49 年入国军吗 - V2EX 推广一下自己刚撸的 IDEA 插件—Bean Copy 助手 - V2EX 几千万的数据量,层级权限下的列表展示页查询应该怎么设计? - V2EX 大家生产环境会一直开着 JFR 吗 - V2EX 有没有新版 Java 的佬(jdk25 和 21 这种), 上手还是走官方文档吗 - V2EX Java 程序员面经怎么准备 - V2EX
用 netty 编写代理服务器,切换出口 IP,不能及时生效 - V2EX
montaro2017 · 2025-10-30 · via Java

公司有一台服务器,有很多个公网 IP ,就想着能不能利用起来。

然后现在有一个任务是用浏览器打开指定网址,返回网页源代码,我就打算把这个服务器做成代理服务器。

本来是计划每个 IP 做一个代理服务器,代理服务器根据入口 IP 用对应的 IP 连接目标服务器。

开启每个浏览器的时候设置代理地址,比如 1.2.3.4:8639, 1.2.3.5:8639 这样 。

然后发现多开浏览器非常吃性能,要充分利用所有的公网 IP 得开几十个浏览器,这时候已经卡到动不了了,肯定不行。

所以我就想开 4 个浏览器,每个浏览器设置一个代理,然后通过接口去切换代理后端的出口。

代理服务器是用 netty 写的,逻辑改成了绑定不同端口,然后通过接口指定端口号和出口 IP ,来切换不同端口对应代理的出口 IP 。

其实就是存了一个 Map<Integer, String>,调接口修改这个 map ,netty 代理服务器连接目标服务器的时候使用出口地址去连接。

现在问题在于,调用接口切换出口 IP 后,日志显示已经使用新的出口 IP 了,但是访问查询 IP 的网站,还是使用之前的 IP ,好像要等一段时间才生效,这是什么问题,求各位大佬指教

@Log4j2
public class ProxyFrontendHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final AddressFunction addressFunction;

    public ProxyFrontendHandler(AddressFunction addressFunction) {
        this.addressFunction = addressFunction;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) {
        if (HttpMethod.CONNECT.equals(req.method())) {
            handleConnectRequest(ctx, req);
            return;
        }
        handleHttpRequest(ctx, req);
    }

    private void handleConnectRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        List<String> split = StrUtil.split(req.uri(), ":");
        String host = CollUtil.getFirst(split);
        int port = Convert.toInt(CollUtil.get(split, 1), 443);

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(ctx.channel().eventLoop())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new RelayHandler(ctx.channel()));
                    }
                });

        ChannelFuture connectFuture;
        InetSocketAddress remoteAddress = new InetSocketAddress(host, port);
        InetSocketAddress sourceAddress = addressFunction.apply(ctx);
        if (sourceAddress != null) {
            log.info("Using outbound: {} | host: {}", sourceAddress, remoteAddress.getHostString());
            connectFuture = bootstrap.connect(remoteAddress, sourceAddress);
        } else {
            connectFuture = bootstrap.connect(remoteAddress);
        }
        connectFuture.addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                Channel outboundChannel = future.channel();
                DefaultFullHttpResponse response = new DefaultFullHttpResponse(
                        HttpVersion.HTTP_1_1,
                        HttpResponseStatus.OK
                );
                response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
                ctx.writeAndFlush(response).addListener((ChannelFutureListener) f -> {
                    try {
                        ctx.pipeline().remove(HttpServerCodec.class);
                        ctx.pipeline().remove(HttpObjectAggregator.class);
                        ctx.pipeline().addLast(new RelayHandler(outboundChannel));
                    } catch (Exception ignored) {
                    }
                });
            } else {
                sendErrorResponse(ctx, "无法连接到目标服务器");
                closeOnFlush(ctx.channel());
            }
        });
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        String host = req.headers().get(HttpHeaderNames.HOST);
        if (host == null) {
            sendErrorResponse(ctx, "缺少 Host 头");
            closeOnFlush(ctx.channel());
            return;
        }
        String[] hostParts = host.split(":");
        String targetHost = hostParts[0];
        int targetPort = hostParts.length > 1 ? Integer.parseInt(hostParts[1]) : 80;
        // 修改请求 URI 为绝对路径
        req.setUri(req.uri().replace("http://" + host, ""));
        req.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);

        // 复制请求以避免在异步操作期间被释放
        FullHttpRequest copiedReq = req.copy();
        // 创建到目标服务器的连接
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(ctx.channel().eventLoop())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new HttpClientCodec());
                        ch.pipeline().addLast(new HttpObjectAggregator(1024 * 1024)); // 增加到 1MB
                        ch.pipeline().addLast(new RelayHandler(ctx.channel()));
                    }
                });
        ChannelFuture connectFuture;
        InetSocketAddress remoteAddress = new InetSocketAddress(targetHost, targetPort);
        InetSocketAddress sourceAddress = addressFunction.apply(ctx);
        if (sourceAddress != null) {
            log.info("Using outbound: {} | host: {}", sourceAddress, remoteAddress.getHostString());
            connectFuture = bootstrap.connect(remoteAddress, sourceAddress);
        } else {
            connectFuture = bootstrap.connect(remoteAddress);
        }
        connectFuture.addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                future.channel().writeAndFlush(copiedReq);
            } else {
                closeOnFlush(ctx.channel());
            }
            if (copiedReq.refCnt() != 0) {
                copiedReq.release();
            }
        });
    }

    private void sendErrorResponse(ChannelHandlerContext ctx, String message) {
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1,
                HttpResponseStatus.BAD_GATEWAY,
                Unpooled.wrappedBuffer(message.getBytes())
        );
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        ctx.writeAndFlush(response);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (cause instanceof SocketException) {
            closeOnFlush(ctx.channel());
            return;
        }
        log.error(cause.getMessage());
        closeOnFlush(ctx.channel());
    }

    private void closeOnFlush(Channel ch) {
        if (ch.isActive()) {
            ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }
}
@Log4j2
public class RelayHandler extends ChannelInboundHandlerAdapter {

    private final Channel relayChannel;

    public RelayHandler(Channel relayChannel) {
        this.relayChannel = relayChannel;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.read();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (relayChannel.isActive()) {
            relayChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    ctx.read(); // 继续读取数据
                } else {
                    future.channel().close();
                }
            });
        } else {
            closeOnFlush(ctx.channel());
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error(cause);
        closeOnFlush(ctx.channel());
    }

    private void closeOnFlush(Channel ch) {
        if (ch.isActive()) {
            ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }
}