【问题标题】:iOS sockets IPv6 SupportiOS 套接字 IPv6 支持
【发布时间】:2016-07-13 19:38:42
【问题描述】:

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

然后,ios 应用程序调用 SocketSender 类(包含在标头搜索路径中)上的 Connect,该类依次调用 Socket.h 的连接函数,然后检查结果。

连接 - 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,那将有很大帮助。

【问题讨论】:

  • 您到底有什么问题?你说有错误,但你没有说错误实际上是什么,或者哪一行代码报告了错误。无论如何,您的gethostbyname() 代码一开始就被破坏了。您没有检查 hostinfo-&gt;h_addrtype 字段以确保 IP 地址(是的,复数)与您期望的类型相同,并且您没有遍历整个列表。 gethostbyname() 可以返回 IPv4 或 IPv6 地址,您无法控制报告的类型。 getaddrinfo() 提供该控制,所以是的,请使用它。
  • @RemyLebeau 当我使用 getaddrinfo() 并遍历列表并使用 socket.h(网络库)中的连接函数进行连接时。连接函数返回 -1 表示它没有连接。为了澄清,我正在尝试对 ipv6 执行与 socketsender.cpp 相同的操作,但它没有连接。
  • 你试过检查errno了吗?它会告诉你为什么connect() 失败。您是否尝试记录getaddrinfo() 报告的 IP 地址?您的设备是否连接到可以访问这些 IP 地址的网络?
  • @3rdeye7:如果您在使用getaddrinfo 返回的地址进行连接时遇到问题,并且您将 IPv4 文字和数字端口传递给 getaddrinfo,您可能会遇到this bug ,它返回具有 0 个端口的结构;获得结构后,您必须手动将端口设置为正确的数字,以解决此错误。
  • @user102008 非常感谢,这正是它,终于让它工作了。不敢相信我没有在开发者论坛上遇到过。我添加了一个答案以供任何遇到相同/类似问题的人参考。

标签: c++ ios sockets ipv6


【解决方案1】:

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

这与客户端-服务器应用程序有关。我的应用程序是一个客户端应用程序,它连接到远程位置的 IPv4 服务器/系统。我们的产品还没有支持 IPv6,包括客户端(iOS、android、windows、unix)和服务器(windows 和 unix),但会在未来的版本中支持。提供这种支持的原因完全是因为 Apple 改变了他们的 Apple 审核流程环境。

方法、提示和问题

  1. Apple 提供了一种方法来测试 IPv6 与您的应用程序的兼容性。这是使用 NAT64/DNS64 从以太网共享您的连接。这对我来说失败了很多次。在研究并重置了我的 SMC 之后,我遇到了 this article 并意识到我可能对配置搞砸了太多。所以我重置了我的 SMC,重新启动并创建了 Internet 共享主机。在对 Internet 共享进行任何更改之前,请务必记住关闭 WiFi。
  2. 要求用户使用 IPv4 IP 地址连接到服务器。该应用程序在 IPv4 网络上完美运行,但在 IPv6 网络中失败。这是由于应用程序没有解析 IP 地址文字。我的应用程序使用的网络库是一个作为预处理宏包含的 cpp 库。最大的烦恼之一是尝试调试,因为您无法调试编译时代码。所以我所做的就是将我的 cpp 文件及其标题移到项目中(幸运的是它只有 3 个文件)。
  3. 对传递端口号的任何人都很重要。 这与#2 和解析 IPv4 文字有关。我在网络概述(清单 10-1)中使用了苹果的精确实现。每次我测试时,连接函数都返回-1,这意味着它没有连接。感谢@user102008 为我提供了this article,我意识到在尝试为端口传递字符串文字时,getaddrinfo 的苹果实现被破坏了。是的,他们要求一个常量字符,即使在尝试 c_str() 时它仍然会返回端口号 0。出于这个原因,我注意到答案并解决了无数网络问题的苹果开发人员提供了一种解决方法。这解决了我的端口不断返回 0 的问题,代码也在下面发布。我所做的只是将它添加到我的网络类(SocketSender.cpp)中,而不是在 Connect 中调用 getaddrinfo,我调用了 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 Programming, UserLevel IPv6 Intro, Porting Applications to IPv6

【讨论】:

  • 许多移动网络仅支持 IPv6。其余的都是双栈。所有网络都执行一些 NAT 转换,有时不止一次,以使 IPv4 流量正常工作。如果您的连接两端都支持 IPv6,您将看到延迟减少和性能提升。
猜你喜欢
  • 2016-11-02
  • 2017-11-05
  • 1970-01-01
  • 2014-03-04
  • 1970-01-01
  • 2018-06-01
  • 2010-12-04
  • 2010-12-01
  • 1970-01-01
相关资源
最近更新 更多