公司有一台服务器,有很多个公网 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); } }}
