当我尝试连接到我的 ipv4 服务器时出现错误。目前,ios 应用程序用户需要输入其服务器的 IP 地址、端口和帐户信息。
然后 ios 应用程序调用 SocketSender 类(包含在 header 搜索路径中)上的 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,那将会很有帮助。
最佳答案
因此,在测试了不同的方法并熟悉网络 (POSIX) 两周之后,我终于让这个工作主要归功于 @user102008 的建议。
这与客户端-服务器应用程序相关。我的应用程序是连接到远程位置的 IPv4 服务器/系统的客户端应用程序。我们尚未为我们的产品支持 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 文件及其 header 移动到项目中(幸运的是它只有 3 个文件)。
对于传递端口号很重要。这与 #2 相关并解析 IPv4 文字。我在网络概览( list 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; }
我实际上将 IP (address.sin_addr.s_addr) 保存在一个 long 数据类型中,它是一个私有(private)变量 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
关于c++ - iOS 套接字 IPv6 支持,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38360309/