天天看點

解決App Store 上架 IOS 程式必須支援IPV6

最近在将自己的ios程式釋出到app store, 其中遇到不少坑, 這裡記錄一下關于app 的純ipv6環境下網絡通路的問題;

首先聲明一下, 純ipv6環境下是否能通路到你的服務,和你的伺服器有沒有ipv6位址沒多大關系,這個問題不應該從伺服器着手處理,而是我們寫的代碼沒有支援ipv6 !;

下面提到的方法同樣适用與mac os x 程式;

如果你的程式是因為不支援純ipv6 環境被拒絕的話, 那麼收到的郵件應該如下:

Guideline  - Performance - App Completeness


We discovered one or more bugs in your app when reviewed on iPad running iOS  on Wi-Fi connected to an IPv6 network.

Specifically, we were unable to login during the review.

Please see attached screenshots for details.

Next Steps

To resolve this issue, please run your app on a device to identify any issues, then revise and resubmit your app for review.

If we misunderstood the intended behavior of your app, please reply to this message in Resolution Center to provide information on how these features were intended to work.

For new apps, uninstall all previous versions of your app from a device, then install and follow the steps to reproduce the issue. For updates, install the new version as an update to the previous version, then follow the steps to reproduce the issue.

Resources

For information about testing your app and preparing it for review, please see Technical Note TN2431: App Testing Guide. 

For a networking overview, please review About Networking. For a more specific overview of App Review’s IPv6 requirements, please review the IPv6 and App Review discussion on the Apple Developer Forum.
           

我的伺服器是使用的阿裡雲的windows server 2008,在國内的網絡環境(ipv4)不論什麼姿勢都是能通路的, 可是國外的網絡大多數已經切換到ipv6下;

直接上解決方案吧, 我沒東西可以扯了, 初寫部落格,文筆不行!

首先,如果你的代碼中通路伺服器的位址是ip位址, 如:

NSString *encodedValue =
    [@"http://123.123.88.127:8080/test"
     stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURLRequest *urlRequest =
    [NSURLRequest requestWithURL:
     [NSURL URLWithString:encodedValue]];

    NSURLResponse *response = nil;
    NSError *error = nil;
    NSData *data = [NSURLConnection
                    sendSynchronousRequest:urlRequest
                                          returningResponse:&response
                                                      error:&error];
    if(error)
    {
        int a = ;
    }
           

那麼, 請為你的伺服器配置一個域名就ok了;

我的程式中還用到了socket 通信,底層用c++編寫了部分功能, 這個時候隻要在通路的時候将ipv4位址轉換成ipv6的就OK啦;

下面展示了我測試的代碼片段;

TestIPV6.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <err.h>

@interface TestIPV6 : NSObject
+ (void)testMain;
@end
           

TestIPV6.m

#import "TestIPV6.h"

@interface TestIPV6()
{

}
//擷取不同網絡環境的IP位址
+ (NSString *)getSockAddrIPString:(const struct sockaddr *)sa;
//根據不同的網絡環境設定端口
+ (void)setPort:(uint16_t)port forSockAddr:(const struct sockaddr *)sa;
@end

@implementation IPv6Tester

+ (NSString *)getSockAddrIPString:(const struct sockaddr *)sa
{
    NSString *result = @"";
    sa_family_t netType = sa->sa_family;
    if(netType == AF_INET)
    {
        char ipv4_str_buf[INET_ADDRSTRLEN] = {};
        struct sockaddr_in *v4sa = (struct sockaddr_in *)sa;

        inet_ntop(AF_INET,//
                  &(v4sa->sin_addr),//
                  ipv4_str_buf,//
                  sizeof(ipv4_str_buf));

        result = [[NSString alloc] initWithCString:ipv4_str_buf encoding:NSUTF8StringEncoding];
    }
    else if(netType == AF_INET6)
    {
        char ipv6_str_buf[INET6_ADDRSTRLEN] = {};
        struct sockaddr_in6 *v6sa = (struct sockaddr_in6 *)sa;

        inet_ntop(AF_INET6,//
                  &(v6sa->sin6_addr),//
                  ipv6_str_buf,//
                  sizeof(ipv6_str_buf));

        result = [[NSString alloc] initWithCString:ipv6_str_buf encoding:NSUTF8StringEncoding];
    }
    return result;
}

+ (void)setPort:(uint16_t)port forSockAddr:(const struct sockaddr *)sa
{
    sa_family_t netType = sa->sa_family;
    if(netType == AF_INET)
    {
        struct sockaddr_in *v4sa = (struct sockaddr_in *)sa;
        v4sa->sin_port = htons(port);
    }
    else if(netType == AF_INET6)
    {
        struct sockaddr_in6 *v6sa = (struct sockaddr_in6 *)sa;
        v6sa->sin6_port = htons(port);
    }
}

+ (void)testMain
{
    struct addrinfo res0;
    struct addrinfo *res1 = ;
    struct addrinfo *res2 = ;

    const char *cause = ;
    //這裡更換成你的伺服器ip位址
    const char *address = "xxx.xx.xx.xxx";
    //這裡是服務端口
    NSUInteger port = ;

    memset(&res0, , sizeof(res0));
    res0.ai_family = PF_UNSPEC;
    res0.ai_socktype = SOCK_STREAM;
    res0.ai_flags = AI_DEFAULT;

    int error = getaddrinfo(address, NULL, &res0, &res1);
    if (error)
    {
        //沒有連接配接上
        errx(, "%s", gai_strerror(error));
    }

    int s = -;
    for (res2 = res1; res2; res2 = res2->ai_next)
    {
        s = socket(res2->ai_family,
                   res2->ai_socktype,
                   res2->ai_protocol);
        if (s < )
        {
            cause = "socket";
            continue;
        }

        struct sockaddr *addr =  (struct sockaddr *)res2->ai_addr;
        [TestIPV6 setPort:(uint16_t)port forSockAddr:addr];

        NSString *ipString = [TestIPV6 getSockAddrIPString:addr];
        NSLog(@"嘗試連接配接位址 : %@", ipString);

        if (connect(s, res2->ai_addr, res2->ai_addrlen) < )
        {
            cause = "connect";
            close(s);
            s = -;
            continue;
        }
        break;
    }

    if(s >= )
    {
        UIAlertView *alertview = [[UIAlertView alloc]
                                  initWithTitle:@"提示"
                                  message:@"連接配接成功"
                                  delegate:self
                                  cancelButtonTitle:@"取消"
                                  otherButtonTitles:@"好的", nil];
        [alertview show];
    }
    else
    {
        //沒有連接配接上
        UIAlertView *alertview = [[UIAlertView alloc]
                                  initWithTitle:@"提示"
                                  message:@"連接配接失敗"
                                  delegate:self
                                  cancelButtonTitle:@"取消"
                                  otherButtonTitles:@"好的", nil];
        [alertview show];
        err(, "%s", cause);
    }
    freeaddrinfo(res1);
}

@end
           

使用這樣的方式就可以連接配接到我的阿裡雲伺服器了;

如果你的伺服器沒有域名的話就需要采用第二種方法去通路