iOS 套接字 IPv6 支持
iOS sockets IPv6 Support
当我尝试连接到我的 ipv4 服务器时出现错误。目前,ios 应用程序用户需要输入其服务器的 IP 地址、端口和帐户信息。
ios 应用然后调用 SocketSender class 上的连接(包含在 header 搜索路径中),后者又调用 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,那将很有帮助。
因此,在测试了不同的方法并熟悉网络 (POSIX) 两周后,我终于让这个工作主要归功于 @user102008 的建议。
这与 Client-Server 应用程序相关。我的应用程序是一个连接到远程位置的 IPv4 server/system 的客户端应用程序。我们还没有为我们的产品支持 IPv6,包括客户端(iOS、android、windows、unix)和服务器(windows & unix),但将来会支持发布。这种支持的原因完全是因为 Apple 改变了他们的 apple 审核流程环境。
方法、技巧和问题
- Apple 提供了一种方法来测试 IPv6 与您的应用程序的兼容性。这是使用 NAT64/DNS64 从您的以太网共享您的连接。这对我来说失败了很多次。在研究并重置我的 SMC 之后,我遇到了 this article 并意识到我可能已经把配置搞得一团糟了。所以我重置了我的 SMC,重新启动并创建了互联网共享主机。在对 Internet 共享进行任何更改之前,请务必记住关闭 WiFi。
- 用户需要使用 IPv4 IP 地址连接到服务器。应用程序 运行 在 IPv4 网络上完美运行,但在 IPv6 网络上失败。这是由于应用程序未解析 IP 地址文字。我的应用程序使用的网络库是一个作为预处理宏包含的 cpp 库。最大的烦恼之一是尝试调试,因为您无法调试编译时代码。所以我所做的是将我的 cpp 文件及其 headers 移到项目中(幸运的是它只有 3 个文件)。
对于传递端口号很重要。 这与 #2 相关并解析 IPv4 文字。我在网络概览(清单 10-1)中使用了苹果的精确实现。每次我测试时,连接函数都是 returning -1,这意味着它没有连接。感谢@user102008 为我提供 this article,我意识到在尝试为端口传递字符串文字时,苹果对 getaddrinfo 的实现被破坏了。是的,他们要求一个常量字符,即使在尝试 c_str() 时它仍然 return 端口号为 0。出于这个原因,我注意到答案并解决了无数网络问题的苹果开发人员提供了变通。这解决了我的端口问题连续 returning 0,下面也发布了代码。我所做的只是将其添加到我的网络 class (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;
}
我其实把IP(address.sin_addr.s_addr)保存在一个long数据类型是私有变量,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);
相关指南 Beej's Guide to Network Programming, UserLevel IPv6 Intro, Porting Applications to IPv6
当我尝试连接到我的 ipv4 服务器时出现错误。目前,ios 应用程序用户需要输入其服务器的 IP 地址、端口和帐户信息。
ios 应用然后调用 SocketSender class 上的连接(包含在 header 搜索路径中),后者又调用 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,那将很有帮助。
因此,在测试了不同的方法并熟悉网络 (POSIX) 两周后,我终于让这个工作主要归功于 @user102008 的建议。
这与 Client-Server 应用程序相关。我的应用程序是一个连接到远程位置的 IPv4 server/system 的客户端应用程序。我们还没有为我们的产品支持 IPv6,包括客户端(iOS、android、windows、unix)和服务器(windows & unix),但将来会支持发布。这种支持的原因完全是因为 Apple 改变了他们的 apple 审核流程环境。
方法、技巧和问题
- Apple 提供了一种方法来测试 IPv6 与您的应用程序的兼容性。这是使用 NAT64/DNS64 从您的以太网共享您的连接。这对我来说失败了很多次。在研究并重置我的 SMC 之后,我遇到了 this article 并意识到我可能已经把配置搞得一团糟了。所以我重置了我的 SMC,重新启动并创建了互联网共享主机。在对 Internet 共享进行任何更改之前,请务必记住关闭 WiFi。
- 用户需要使用 IPv4 IP 地址连接到服务器。应用程序 运行 在 IPv4 网络上完美运行,但在 IPv6 网络上失败。这是由于应用程序未解析 IP 地址文字。我的应用程序使用的网络库是一个作为预处理宏包含的 cpp 库。最大的烦恼之一是尝试调试,因为您无法调试编译时代码。所以我所做的是将我的 cpp 文件及其 headers 移到项目中(幸运的是它只有 3 个文件)。
对于传递端口号很重要。 这与 #2 相关并解析 IPv4 文字。我在网络概览(清单 10-1)中使用了苹果的精确实现。每次我测试时,连接函数都是 returning -1,这意味着它没有连接。感谢@user102008 为我提供 this article,我意识到在尝试为端口传递字符串文字时,苹果对 getaddrinfo 的实现被破坏了。是的,他们要求一个常量字符,即使在尝试 c_str() 时它仍然 return 端口号为 0。出于这个原因,我注意到答案并解决了无数网络问题的苹果开发人员提供了变通。这解决了我的端口问题连续 returning 0,下面也发布了代码。我所做的只是将其添加到我的网络 class (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; }
我其实把IP(address.sin_addr.s_addr)保存在一个long数据类型是私有变量,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);
相关指南 Beej's Guide to Network Programming, UserLevel IPv6 Intro, Porting Applications to IPv6