2016-07-13 42 views
0

當我嘗試連接到我的ipv4服務器時出現錯誤。目前,ios應用程序用戶需要輸入服務器的IP地址,端口和帳戶信息。iOS套接字IPv6支持

然後,ios應用程序會調用SocketSender類中的Connect(包含在標頭搜索路徑中),然後調用Socket.h的connect函數,然後檢查結果。

連接 - SocketSender.cpp

bool SocketSender::Connect (const char *host, int port, CApiError &err) 
{ 

errno = 0; 
struct hostent *hostinfo; 

hostinfo = gethostbyname (host); 

if (!hostinfo) { 
#ifdef PLATFORM_WIN32 
m_nLastErrorNo = SOCKET_ERRNO(); 
err.SetSystemError(m_nLastErrorNo); 
#else 
/* Linux stores the gethostbyname error in h_errno. */ 
m_nLastErrorNo = EINVAL; // h_errno value is incompatible with the "normal" error codes 
err.SetError(FIX_SN(h_errno, hstrerror(h_errno)), CATEGORY_SYSTEM | ERR_TYPE_ERROR); 
#endif 
return false; 
} 

socket_fd = socket (AF_INET, SOCK_STREAM, 0); 

if (socket_fd == -1) { 
    m_nLastErrorNo = SOCKET_ERRNO(); 
    err.SetSystemError(m_nLastErrorNo); 
    return false; 
} 

struct sockaddr_in address; 

address.sin_family = AF_INET; 
address.sin_port = htons (port); 
address.sin_addr = *(struct in_addr *) *hostinfo->h_addr_list; 

int result; 

SetSocketOptions(); 

result = connect (socket_fd, (struct sockaddr *) &address, sizeof (address)); 

if (result == -1) { 
if (IS_IN_PROGRESS()) { 
    fd_set f1,f2,f3; 
    struct timeval tv; 

    /* configure the sets */ 
    FD_ZERO(&f1); 
    FD_ZERO(&f2); 
    FD_ZERO(&f3); 
    FD_SET(socket_fd, &f2); 
    FD_SET(socket_fd, &f3); 

    /* we will have a timeout period */ 
    tv.tv_sec = 5; 
    tv.tv_usec = 0; 

    int selrez = select(socket_fd + 1,&f1,&f2,&f3,&tv); 

    if (selrez == -1) { // socket error 
    m_nLastErrorNo = SOCKET_ERRNO(); 
    Disconnect(true); 
    err.SetSystemError(m_nLastErrorNo); 
    return false; 
    } 

    if (FD_ISSET(socket_fd, &f3)) { // failed to connect .. 
    int sockerr = 0; 
#ifdef PLATFORM_WIN32 
    int sockerr_len = sizeof(sockerr); 
#else 
    socklen_t sockerr_len = sizeof(sockerr); 
#endif 
    getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &sockerr_len); 
    if (sockerr != 0) { 
     m_nLastErrorNo = sockerr; 
    } else { 
#ifdef PLATFORM_WIN32 
     m_nLastErrorNo = ERROR_TIMEOUT; // windows actually does not specify the error .. is this ok? 
#else 
     m_nLastErrorNo = ETIMEDOUT; 
#endif 
    } 
    Disconnect(true); 
    err.SetSystemError(m_nLastErrorNo); 
    return false; 
    } 

    if (!FD_ISSET(socket_fd, &f2)) { // cannot read, so some (unknown) error occured (probably time-out) 
    int sockerr = 0; 
#ifdef PLATFORM_WIN32 
    int sockerr_len = sizeof(sockerr); 
#else 
    socklen_t sockerr_len = sizeof(sockerr); 
#endif 
    getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &sockerr_len); 
    if (sockerr != 0) { 
     m_nLastErrorNo = sockerr; 
    } else { 
#ifdef PLATFORM_WIN32 
     m_nLastErrorNo = ERROR_TIMEOUT; // windows actually does not specify the error .. is this ok? 
#else 
     m_nLastErrorNo = ETIMEDOUT; 
#endif 
    } 
    Disconnect(true); 
    err.SetSystemError(m_nLastErrorNo); 
    return false; 
    } 
#ifndef PLATFORM_WIN32 // FIXME: is the same needed for windows ? 

    // unix always marks socket as "success", however error code has to be double-checked 
    int error = 0; 
    socklen_t len = sizeof(error); 
    if (getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { 
    err.SetSystemError(); 
    return false; 
    } 
    if(error != 0) { 
    m_nLastErrorNo = error; 
    Disconnect(true); 
    err.SetSystemError(m_nLastErrorNo); 
    return false; 
    } 
#endif 
} else { 
    m_nLastErrorNo = SOCKET_ERRNO(); 
    Disconnect(true); 
    err.SetSystemError(m_nLastErrorNo); 
    return false; 
} 
} 

m_nIP = ntohl(address.sin_addr.s_addr); 

m_bServerSocket = false; 
return true; 
} 

那就是工作沒有任何問題的原始版本。當我改變上述使用AF_INET6和in_addr6-> sin6_addr時,我不斷收到錯誤,應用程序無法連接。我嘗試使用getaddrinfo,但這仍然沒有連接。

struct addrinfo hints, *res, *res0; 
int error; 
const char *cause = NULL; 

memset(&hints, 0, sizeof(hints)); 
hints.ai_family = PF_UNSPEC; 
hints.ai_socktype = SOCK_STREAM; 
hints.ai_flags = AI_DEFAULT; 
error = getaddrinfo(host, "PORT", &hints, &res0); 
if (error) { 
    errx(1, "%s", gai_strerror(error)); 
    /*NOTREACHED*/ 
} 
socket_fd = -1; 
printf("IP addresses for %s:\n\n", host); 
int result; 
void *addr; 
char *ipver; 
for (res = res0; res!=NULL; res = res->ai_next) { 
    socket_fd = socket(res->ai_family, res->ai_socktype, 
       res->ai_protocol); 
    if (socket_fd < 0) { 
     cause = "socket"; 
     continue; 
    } 

    if ((result = connect(socket_fd, res->ai_addr, res->ai_addrlen)) < 0) { 
     cause = "connect"; 
     close(socket_fd); 
     socket_fd = -1; 
     continue; 
    } 
    // get the pointer to the address itself, 
    // different fields in IPv4 and IPv6: 
    if (res->ai_family == AF_INET) { // IPv4 
     struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr; 
     addr = &(ipv4->sin_addr); 
     ipver = "IPv4"; 
    } else { // IPv6 
     struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr; 
     addr = &(ipv6->sin6_addr); 
     ipver = "IPv6"; 
    } 
    SetSocketOptions(); 
    break; /* okay we got one */ 
} 

我需要使它向後兼容ipv6和ipv4。任何幫助將不勝感激,因爲我一直在過去一週卡住測試。此外,如果有人知道如何調試XCode上的SocketSender.cpp,這將是很多的幫助。

+0

你到底是有什麼問題有?你說有錯誤,但你沒有說錯誤是什麼,或者哪一行代碼報告錯誤。在任何情況下,您的'gethostbyname()'代碼都被打破。您沒有檢查'hostinfo-> h_addrtype'字段以確保IP地址(是),複數)與您期望的類型相同,並且您沒有在整個列表中循環。 'gethostbyname()'可能會返回* IPv4或IPv6地址,並且您無法控制報告的類型。 'getaddrinfo()'提供了控制權,所以是的,使用它。 –

+0

@RemyLebeau當我使用getaddrinfo()並遍歷列表並使用socket.h(網絡庫)中的連接函數進行連接時。連接函數返回-1,這意味着它沒有連接。爲了澄清,我正在嘗試爲ipv6執行與socketsender.cpp相同的操作,但它沒有連接。 – 3rdeye7

+0

你有沒有嘗試檢查'errno'呢?它會告訴你爲什麼'connect()'失敗。您是否嘗試記錄'getaddrinfo()'報告的IP地址?您的設備是否連接到可以訪問這些IP地址的網絡? –

回答

1

因此,經過兩週的測試不同的方法和熟悉網絡(POSIX)我終於得到這個工作主要是由於@ user102008的建議。

這與客戶端 - 服務器應用程序有關。我的應用程序是連接到遠程位置的IPv4服務器/系統的客戶端應用程序。我們尚未支持包括客戶端(iOS,安卓,Windows,Unix)和服務器(Windows & unix)的產品在內的IPv6,但將支持未來版本。這種支持的原因完全是由於蘋果改變了他們的蘋果審查流程環境。

方法,技巧和問題

  1. 蘋果提供了一個方法來測試您的應用的IPv4兼容。這是使用NAT64/DNS64從您的以太網共享您的連接。這對我來說失敗了很多次。經過研究和重新設置我的SMC後,我碰到了this article,並意識到我可能一直在搞太多配置。所以我重置我的SMC,重新啓動並創建互聯網共享主機。請務必在更改互聯網共享之前關閉WiFi。
  2. 用戶需要使用IPv4 IP地址連接到服務器。該應用程序在IPv4網絡上運行完美,但在IPv6網絡中失敗。這是由於該應用程序未解析IP地址文字。我的應用程序使用的網絡庫是一個包含在預處理宏中的cpp庫。最大的煩惱之一是試圖調試,因爲你無法調試編譯時代碼。所以我做的是將我的cpp文件和它們的頭文件一起移動到項目中(幸運的是它只有3個文件)。
  3. 重要的是通過無線端口號碼。這與#2綁定並解析了IPv4文字。我在網絡概述中使用了蘋果確切的實現(列表10-1)。每次我測試時,connect函數都返回-1,表示它沒有連接。感謝@ user102008爲我提供了this article,我意識到嘗試爲端口傳遞字符串文字時,蘋果的getaddrinfo實現中斷了。是的,他們要求一個常量字符,即使在嘗試c_str()時,它仍然會返回端口號0.由於這個原因,一位蘋果開發人員發現了答案,並解決了無數的網絡問題,提供瞭解決方法。這固定了我的端口不斷返回0,代碼也發佈在下面。我所做的只是將其添加到我的網絡類(SocketSender.cpp)中,而不是在Connect中調用getaddrinfo,我調用get getdrinfo_compat。這使我能夠在IPv4和IPv6網絡中完美連接。

    static int getaddrinfo_compat( 
    const char * hostname, 
    const char * servname, 
    const struct addrinfo * hints, 
    struct addrinfo ** res 
    ) { 
        int err; 
        int numericPort; 
    
        // If we're given a service name and it's a numeric string, set `numericPort` to that, 
        // otherwise it ends up as 0. 
    
        numericPort = servname != NULL ? atoi(servname) : 0; 
    
        // Call `getaddrinfo` with our input parameters. 
    
        err = getaddrinfo(hostname, servname, hints, res); 
    
        // Post-process the results of `getaddrinfo` to work around <rdar://problem/26365575>. 
    
    if ((err == 0) && (numericPort != 0)) { 
    for (const struct addrinfo * addr = *res; addr != NULL; addr = addr->ai_next) { 
        in_port_t * portPtr; 
    
        switch (addr->ai_family) { 
         case AF_INET: { 
          portPtr = &((struct sockaddr_in *) addr->ai_addr)->sin_port; 
         } break; 
         case AF_INET6: { 
          portPtr = &((struct sockaddr_in6 *) addr->ai_addr)->sin6_port; 
         } break; 
         default: { 
          portPtr = NULL; 
         } break; 
        } 
        if ((portPtr != NULL) && (*portPtr == 0)) { 
         *portPtr = htons(numericPort); 
        } 
    } 
    } 
    return err; 
    } 
    
  4. 其實我保存在一個私有變量,m_nIP長數據類型的IP(address.sin_addr.s_addr)。問題是我不需要IPv6,因爲我們的整個產品組都使用IPv4。下面的代碼解決了這個問題。

    const uint8_t *bytes = ((const struct sockaddr_in6 *)addrPtr)->sin6_addr.s6_addr; 
    bytes += 12; 
    struct in_addr addr = { *(const in_addr_t *)bytes }; 
    m_nIP = ntohl(addr.s_addr); 
    
  5. 相關指南Beej's Guide to Network ProgrammingUserLevel IPv6 IntroPorting Applications to IPv6

+0

許多移動網絡僅限於IPv6。所有其餘的人都是雙棧。所有網絡執行一些NAT轉換,有時不止一次,以使IPv4流量工作。如果您在連接的兩端都支持IPv6,則會看到延遲減少和性能更佳。 –