OkHttp RouteSelector代理解析

  • Post author:
  • Post category:其他


我们来看RouteSelector做了什么工作,当new StreamAllocation时,会新建一个RouteSelector

//每一个新连接都会创建一个新的StreamAllocation
public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
      EventListener eventListener, Object callStackTrace) {
    this.connectionPool = connectionPool;
    this.address = address;
    this.call = call;
    this.eventListener = eventListener;
    this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
    this.callStackTrace = callStackTrace;
}

public RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
      EventListener eventListener) {
    this.address = address;
    this.routeDatabase = routeDatabase;
    this.call = call;
    this.eventListener = eventListener;

    resetNextProxy(address.url(), address.proxy());
}

主要是resetNextProxy方法,address对象的url和proxy都是开发者配置进来的,分别是目标主机的url和要使用的代理服务器,如果开发者没有配置proxy,就看proxySelector有没有实现,proxySelector可以给不同的url配置不同的代理,如果proxySelector也没有实现,则使用默认的实现,为Proxy.NO_PROXY不用代理,直连目标主机。

当创建RouteSelector就初始化好了routeSelector.proxies对象,后面会根据这个代理列表来通过代理服务器访问目标地址。

private void resetNextProxy(HttpUrl url, Proxy proxy) {
  if (proxy != null) {
    //如果配置的代理服务器不为空,则使用配置的
    proxies = Collections.singletonList(proxy);
  } else {
    //如果没有配置proxy,则使用proxySelector来选择代理服务器,proxySelector允许开发者自己实现,可以给不同的url配置不同的代理服务器
    //如果开发者没有配置proxySelecor这里默认实现为不使用代理
    List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
    proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
        ? Util.immutableList(proxiesOrNull)
        : Util.immutableList(Proxy.NO_PROXY);
  }
  nextProxyIndex = 0;
}

这里我们看到已经初始化好了proxies列表,我们回到connectInteceptor->streamAllocation的findConnection,获取连接对象的方法

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    
    ....省略代码(主要功能是通过当前地址去连接池中查找可用连接)....

    //从连接池中找不到可用连接
    boolean newRouteSelection = false;
    //通过routeSelector查询dns服务器,解析出目标主机的ip地址,或者代理服务器的ip地址
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      //routeSelection存储下一个代理服务器可用的所有ip地址,没有配置代理服务器则存储的目标主机的可用的所有ip地址
      //routeSelector初始化时就初始化好了一个proxies列表,这个列表存储了代理服务器的列表,每一次next都切换到下一台
      //代理服务器(没有配置代理时则为目标主机),并且解析出当前服务器的所有可用ip,包装到routeSelection中
      routeSelection = routeSelector.next();
    }

    .... 省略代码(主要功能是使用上面的ip地址,重新去连接池找,还是找不到就新建连接,找到则返回)....
  }

看下routeSelection=routeSelector.next()这个代码做了什么工作

public Selection next() throws IOException {
    //如果配置的proxies列表用完了,则报错
    if (!hasNext()) {
        throw new NoSuchElementException();
    }

    //存储下一个代理服务器可用的代理地址
    List<Route> routes = new ArrayList<>();
    while (hasNextProxy()) {
        //获取下一个代理服务器proxy,并通过dns解析它的所有ip地址(可能是服务器集群),将结果保存在inetSocketAddresses列表中
        Proxy proxy = nextProxy();
        //遍历当前代理服务器解析出来的ip地址
        for (int i = 0, size = inetSocketAddresses.size(); i < size; i++) {
            //将每个ip地址保存为Route对象
            Route route = new Route(address, proxy, inetSocketAddresses.get(i));
            if (routeDatabase.shouldPostpone(route)) {
                 //判断当前地址inetSocketAddresses是否之前请求过且失败了,是的话将这个地址做特殊处理
                postponedRoutes.add(route);
            } else {
                routes.add(route);
            }
        }

        //寻找到可用的代理服务器地址,则跳出,要立马去请求了,不能等寻找到所有的地址,再来请求,那样影响效率,如果当前的请求失败,
        //则会记录到routeDatabase中,下次再进来就会通过上面的routeDatabase.shouldPostpone(route)做特殊处理
        if (!routes.isEmpty()) {
            break;
        }
    }

    //如果上面的可用地址都为空,则最后尝试之前失败的代理地址,也就是postponedRoutes里的
    if (routes.isEmpty()) {
        routes.addAll(postponedRoutes);
        postponedRoutes.clear();
    }

    //将当前代理服务器的可用ip地址封装成Selection对象返回
    return new Selection(routes);
}

我们看下Proxy proxy = nextProxy();方法是怎么解析出ip地址的

private Proxy nextProxy() throws IOException {
    if (!hasNextProxy()) {
        throw new SocketException("No route to " + address.url().host()
                                  + "; exhausted proxy configurations: " + proxies);
    }
    //获取下一个代理服务器
    Proxy result = proxies.get(nextProxyIndex++);
    resetNextInetSocketAddress(result);
    return result;
}

再看resetNextInetSocketAddress

private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
    //存储当前代理解析出的所有可用ip地址
    inetSocketAddresses = new ArrayList<>();

    String socketHost;
    int socketPort;
    if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
        //如果proxy配置的是直连,则使用目标地址的域名和端口。
        //socks协议我理解应该是整个网络已经被代理软件代理了,只需要正常的向目标服务器发请求,网络都会通过代理服务,
        //然后代理服务器再解析目标地址去访问
        socketHost = address.url().host();
        socketPort = address.url().port();
    } else {
        SocketAddress proxyAddress = proxy.address();
        if (!(proxyAddress instanceof InetSocketAddress)) {
            throw new IllegalArgumentException(
                "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
        }
        //如果是Http代理,则使用代理服务器的域名和端口
        InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
        socketHost = getHostString(proxySocketAddress);
        socketPort = proxySocketAddress.getPort();
    }

    if (socketPort < 1 || socketPort > 65535) {
        throw new SocketException("No route to " + socketHost + ":" + socketPort
                                  + "; port is out of range");
    }

    if (proxy.type() == Proxy.Type.SOCKS) {
        //socks协议交由代理服务去dns解析
        inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
    } else {
        eventListener.dnsStart(call, socketHost);

        //上面已经将socketHost赋值为目标主机的域名(直连情况)或者代理服务器的域名(代理情况)
        //这里调用dns去解析出ip地址,如果没有实现dns,则使用默认的构造Dns SYSTEM = new Dns() 
        //里面lookup调用的就是安卓的api:InetAddress.getAllByName(hostname),它会解析出要访问的ip地址
        //由于可能是服务器集群,有可能解析出多个ip出来
        List<InetAddress> addresses = address.dns().lookup(socketHost);
        if (addresses.isEmpty()) {
            throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
        }

        eventListener.dnsEnd(call, socketHost, addresses);

        //把目标ip都加入到初始化列表中
        for (int i = 0, size = addresses.size(); i < size; i++) {
            InetAddress inetAddress = addresses.get(i);
            inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
        }
    }
}

至此,通过Proxy proxy = nextProxy() –> resetNextInetSocketAddress(Proxy proxy),就把要访问的ip地址(可能是配置的代理服务器,也可以是主机)初始化好,放入RouteSelector的 List inetSocketAddresses中了。剩下的就是把所有InetSocketAddress一一封装成Route组成的列表routes,然后返回new Selection(routes)给到findConnection处。



版权声明:本文为laigengsong原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。