V2EX 10月30日 12:15
利用公网IP搭建代理服务器,实现IP切换与性能优化
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

为了充分利用公司拥有的多个公网IP地址,作者尝试搭建一个代理服务器来处理浏览器访问指定网址并获取源代码的任务。最初的想法是为每个公网IP单独部署一个代理服务器,但发现多开浏览器消耗大量性能。因此,优化方案是仅开启少量浏览器,通过接口动态切换代理服务器后端的出口IP。代理服务器使用Netty编写,通过修改一个Map来管理不同端口与出口IP的映射关系,实现IP的动态切换。然而,在实际测试中,即使日志显示IP已切换,但访问查询IP的网站时仍显示旧IP,作者对此现象寻求技术指导。

💡 **目标与挑战**: 作者旨在利用公司闲置的多个公网IP地址,搭建一个代理服务器来执行浏览器访问并获取网页源代码的任务。核心挑战在于如何高效地利用所有IP,同时避免因开启过多浏览器实例而导致的性能瓶颈。

🚀 **优化方案**: 为解决性能问题,作者调整策略,计划仅启动少量浏览器实例,并通过后端接口动态切换代理服务器的出口IP。这种方式将代理逻辑集中到服务器端,并通过端口和IP映射管理,大大降低了客户端的资源消耗。

🔧 **技术实现**: 代理服务器基于Netty框架开发,核心逻辑在于维护一个`Map`,该Map存储了端口号与对应出口IP的映射关系。通过调用API修改此Map,即可实现代理服务器在连接目标服务器时切换使用指定的出口IP。

❓ **遇到的问题**: 在IP切换功能实现后,尽管服务器日志显示已成功更新出口IP,但实际访问外部IP查询网站时,显示的IP地址并未及时更新,仍为之前的IP。作者对此现象表示困惑,并希望得到解答,了解IP切换不生效的原因及解决方案。

公司有一台服务器,有很多个公网 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 ,好像要等一段时间才生效,这是什么问题,求各位大佬指教

@Log4j2public 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);        }    }}
@Log4j2public 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);        }    }}

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Netty 代理服务器 公网IP IP切换 性能优化 Netty Proxy Server Public IP IP Switching Performance Optimization
相关文章