iOS套接字IPv6支持

时间:2016-07-13 19:38:43

标签: c++ ios sockets ipv6

当我尝试连接到我的ipv4服务器时出错。目前,ios app用户需要输入其服务器的IP地址,端口和帐户信息。

然后,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-&gt; 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,那将是很多帮助。

1 个答案:

答案 0 :(得分:2)

因此,经过两周测试不同的方法并熟悉网络(POSIX)后,我终于得到了这个主要是由于@ user102008建议。

这与客户端 - 服务器应用程序相关。我的应用程序是连接到远程位置的IPv4服务器/系统的客户端应用程序。我们尚未支持IPv6用于我们的产品,包括客户端(iOS,android,windows,unix)和服务器(windows&amp; unix),但将在未来版本中提供支持。这种支持的原因完全是由于Apple改变了他们的苹果审核流程环境。

方法,提示和问题

  1. Apple提供了一种测试IPv6与您的应用程序兼容性的方法。这是使用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 getaddrinfo_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. 我实际上将IP(address.sin_addr.s_addr)保存在一个私有变量m_nIP的长数据类型中。问题是我不需要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