安卓中的IP回退

7

我正在访问一个用于Web服务调用的服务器。 当我在与服务器相同的网络上开发时,我可以通过其内部IP地址访问Web服务,但无法通过其外部IP地址访问。 但是,如果我不在该网络上,则只能通过其外部IP地址访问它。 试一种IP地址并回落到另一个IP地址的最佳方法是什么?

以下是我访问其中一个IP地址的示例代码:

protected String retrieve() {
    Log.v(TAG, "retrieving data from url: " + getURL());

    HttpPost request = new HttpPost(getURL());
    try {
        StringEntity body = new StringEntity(getBody());
        body.setContentType(APPLICATION_XML_CONTENT_TYPE);
        request.setEntity(body);            

        HttpConnectionParams.setConnectionTimeout(client.getParams(), CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(client.getParams(), SOCKET_TIMEOUT);

        HttpResponse response = client.execute(request);
        final int statusCode = response.getStatusLine().getStatusCode();


        if (statusCode != HttpStatus.SC_OK) {
            Log.e(TAG, "the URL " + getURL() + " returned the status code: " + statusCode + ".");
            return null;
        }

        HttpEntity getResponseEntity = response.getEntity();

        if (getResponseEntity != null) {
            return EntityUtils.toString(getResponseEntity);
        }
    } catch (IOException e) {
        Log.e(TAG, "error retrieving data.", e);
        request.abort();
    }

    return null;
}   

/*
 * @return the URL which should be called.
 */
protected String getURL() {
    return INTERNAL_SERVER_URL + WEB_APP_PATH;
}

1
个人而言,我会使用编译时标志,您只能在开发时启用。就像日志输出一样,您很可能不希望您发布的代码尝试连接此内部IP地址,无论对外部IP地址的调用是否成功。无论如何,我会尝试使用 Socket 来在运行时确定这一点。 - harism
我正在使用Maven进行构建,但我对它不是太熟悉。是否有可能以某种方式将其构建为开发版本和生产版本,设置某些内容,我可以在我的代码中访问以查看要访问哪个IP地址? - Zack Marrapese
很不幸,我完全不知道如何使用Maven实现这样的分割。 - harism
你可能可以让你的应用程序判断它是否使用调试密钥或发布密钥进行签名,并根据此采取行动。 - Chris Stratton
你能够访问路由器来解决问题吗?还是你没有路由器访问权限? - Fred Grott
8个回答

4

查看您的安卓设备的IP地址。您可以像这里所述那样获取它。

然后,您可以决定:

  • 如果您在办公室的子网中(例如192.168.0.0/16)- 使用内部地址
  • 如果您在其他子网中 - 使用外部地址

3

在harism的非常好的评论基础上,我会简单地使用静态布尔值来选择IP,从而避免每次都ping错IP:

public static final Boolean IS_DEBUG = true; // or false

// all your code here

if (DEBUG)
    ip = xxx.xxx.xxx.xxx;
else
    ip = yyy.yyy.yyy.yyy;

然后,每次我在家工作而不是在办公室工作时,我都需要更改那个布尔值。 - Zack Marrapese
@Zack,这仍然比条件编译要快。我加入了这个功能是因为你的评论:“是否可能以某种方式构建它作为开发版本和生产版本,设置一些我可以在我的代码中访问的东西来查看要访问哪个IP? - Aleadam

3
这不是一件容易在软件中修复的事情。我认为正确的答案是修复将流量路由到您内部Web服务器的过滤器/配置,或通过正确配置DNS返回适当的IP地址,具体取决于您所在的位置(内部或外部网络)。更多信息可以在此处找到:

使用外部IP地址访问内部网络资源

http://www.astaro.org/astaro-gateway-products/network-security-firewall-nat-qos-ips-more/6704-cant-acces-internal-webserver-via-external-ip.html

通过谷歌搜索类似“内部网络上无法使用外部IP地址”的内容。


3

您可以在IOException的catch子句中放置重试代码。

protected String retrieve(String url) {
    Log.v(TAG, "retrieving data from url: " + url);

    HttpPost request = new HttpPost(url);
    try {
        StringEntity body = new StringEntity(getBody());
        body.setContentType(APPLICATION_XML_CONTENT_TYPE);
        request.setEntity(body);            

        HttpConnectionParams.setConnectionTimeout(client.getParams(), CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(client.getParams(), SOCKET_TIMEOUT);

        HttpResponse response = client.execute(request);
        final int statusCode = response.getStatusLine().getStatusCode();


        if (statusCode != HttpStatus.SC_OK) {
            Log.e(TAG, "the URL " + getURL() + " returned the status code: " + statusCode + ".");
            return null;
        }

        HttpEntity getResponseEntity = response.getEntity();

        if (getResponseEntity != null) {
            return EntityUtils.toString(getResponseEntity);
        }
    } catch (IOException e) {
        if(url.equals(EXTERNAL_URL){
            return retrieve(INTERNAL_URL);
        }

        Log.e(TAG, "error retrieving data.", e);
        request.abort();
    }

    return null;
}   

注意:正如大多数人所说,这可能不是生产发布的好解决方案,但对于测试来说,它可能完全可行。


1

你可以将retrieve()调用更改为接受主机作为参数。在启动时,尝试ping每个可能的主机。对某些返回非常快的内容(比如测试页面)进行简单的retrieve调用。逐个尝试你想要尝试的每个可能的主机。一旦找到一个可用的主机,保存该主机并将其用于所有未来的调用。


1

首先,您应该能够在运行时处理此情况。我强烈建议使用不同的构建版本进行尝试。例如:如果外部失败,则尝试内部。

同时,有一些启发式方法:
内部IP意味着网络相同。因此,您可以检查它,如果相同,请首先尝试内部地址,否则外部地址具有优先权。 稍后,保存本地IP地址与成功连接的IP地址之间的映射,并查找以更改优先级。

解析本身可以通过请求服务器的根目录'/'并设置超时来完成(如果需要,可以使用2个不同的线程同时执行任务)。

此外,如果您可以访问路由器/防火墙,则可以使其识别外部地址并正确处理它。因此,您最终可以获得正常工作的外部地址。


1

我会将这类特定于环境的信息放在属性文件(或任何类型的配置文件)中。你需要的只是一个包含一行内容的文件(显然,你需要将IP地址更改为所需的地址):

serverUrl=192.168.1.1

Java已经内置了读写这些类型文件的功能(请参见上面的链接)。您还可以将数据库连接信息等保存在此文件中。任何特定于环境的内容都可以。

在您的代码中,似乎使用常量来保存服务器URL。我不建议这样做。当服务器URL更改时会发生什么?您需要修改并重新编译代码。然而,使用配置文件则不需要进行任何代码更改。


0
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;

并且:

HttpClient client = new HttpClient();
HttpMethod method = new GetMethod("http://www.myinnersite.com");
int responseCode = client.executeMethod(method);
if (responseCode != 200) {
    method = new GetMethod("http://www.myoutersite.com");
}

等等...


这样行不通。如果在这种情况下DNS没有解析到任何内容,或者在我的情况下我尝试访问的IP不存在,那么就不会返回状态码,因为没有服务器返回状态码...它只会导致SocketTimeoutException。 - Zack Marrapese
这也有一个缺点,就是在转到备用连接之前必须超时第一个连接,这可能需要几秒钟的时间。 - CaseyB
是的,我认为设置一个超时并将第一次调用包装在 try catch 块中可能会解决问题。我现在想不出更好的方法了。 - nickfox

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接