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

推荐订阅源

PCI Perspectives
PCI Perspectives
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Palo Alto Networks Blog
S
Schneier on Security
Scott Helme
Scott Helme
T
Threat Research - Cisco Blogs
K
Kaspersky official blog
Microsoft Azure Blog
Microsoft Azure Blog
T
The Exploit Database - CXSecurity.com
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tenable Blog
G
GRAHAM CLULEY
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
I
Intezer
D
Docker
月光博客
月光博客
L
Lohrmann on Cybersecurity
Latest news
Latest news
B
Blog
罗磊的独立博客
M
MIT News - Artificial intelligence
S
Securelist
Know Your Adversary
Know Your Adversary
Help Net Security
Help Net Security
Recorded Future
Recorded Future
S
SegmentFault 最新的问题
N
Netflix TechBlog - Medium
T
Threatpost
H
Hacker News: Front Page
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
人人都是产品经理
人人都是产品经理
F
Fortinet All Blogs
博客园 - Franky
P
Proofpoint News Feed
大猫的无限游戏
大猫的无限游戏
Blog — PlanetScale
Blog — PlanetScale
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
A
About on SuperTechFans
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tor Project blog
Google Online Security Blog
Google Online Security Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Engineering at Meta
Engineering at Meta
Webroot Blog
Webroot Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Microsoft Security Blog
Microsoft Security Blog

Java

华为的毕昇 JDK 8 AppCDS 特性稳定吗? - V2EX 我把 Java 开发的 kkRepo 支持 AOT 编译, 1s 极速启动,空闲内存小于 200MB - V2EX fastjson1.x 最新版本疑似又出 RCE 漏洞了 - V2EX 讲讲我怎么获得 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);
        }
    }
}