2016-07-13 2 views
0

내 ipv4 서버에 연결하려고하면 오류가 발생합니다. 현재 iOS 앱 사용자는 서버의 IP 주소, 포트 및 계정 정보를 입력해야합니다.iOS 소켓 IPv6 지원

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를 디버깅하는 방법을 알고 있다면.

+0

? 오류가 있다고 말하면서 오류가 실제로 무엇인지 또는 오류를보고하는 코드 행을 말하지 않습니다. 어쨌든'gethostbyname()'코드는 처음부터 깨져있었습니다. 'hostinfo-> h_addrtype' 필드를 검사하지 않고 IP 주소 (예, 복수형)가 예상하고있는 것과 동일한 유형이고 전체 목록에서 루핑하지 않았는지 확인했습니다. 'gethostbyname()'은 * IPv4 또는 IPv6 주소를 반환 할 수 있으며, 당신은 어떤 종류의 보고서를 제어 할 수 없습니다. 'getaddrinfo()'는 그 컨트롤을 제공하므로 네, 사용하십시오. –

+0

@RemyLebeau 내가 getaddrinfo()를 사용하고 목록을 반복하고 socket.h (네트워킹 lib)의 connect 함수를 사용하여 연결합니다. connect 함수가 -1을 반환하면 연결되지 않았다는 의미입니다. 명확히하기 위해 ipv6에 대해 socketsender.cpp와 동일한 작업을 수행하려고 시도하고 있지만 연결되지 않았습니다. – 3rdeye7

+0

아직'errno'를 검사 해 보셨습니까? 'connect()'가 실패하는 이유를 알려줄 것입니다. 'getaddrinfo()'가보고하는 IP 주소를 기록하려고 시도 했습니까? 기기가 해당 IP 주소에 도달 할 수있는 네트워크에 연결되어 있습니까? –

답변

1

다른 접근법을 테스트하고 네트워킹 (POSIX)에 익숙해 진 지 2 주 후에 나는 마침내 @ user102008 제안으로 인해 주로이 작업을 수행하게되었습니다.

이것은 클라이언트 - 서버 응용 프로그램과 관련이 있습니다. 내 응용 프로그램은 원격 위치의 IPv4 서버/시스템에 연결하는 클라이언트 응용 프로그램입니다. 우리는 아직 클라이언트 (iOS, android, windows, unix)와 서버 (windows & unix)를 포함하지만 향후 릴리스에서 지원하는 제품에 대해서는 IPv6을 지원하지 않습니다. 이 지원의 이유는 Apple의 사과 검토 프로세스 환경 변경 때문이었습니다.

접근 방법, 팁 및 문제점

  1. 애플 앱과 IPv6의 호환성을 테스트 할 수있는 방법을 제공하고 있습니다. 이것은 NAT64/DNS64를 사용하여 이더넷에서 연결을 공유합니다. 이것은 나를 위해 여러 번 실패했습니다. SMC를 조사하고 재설정 한 후, 나는 this article을 만나서 구성을 너무 많이 망칠 수 있음을 깨달았습니다. 따라서 SMC를 재설정하고 인터넷 공유 호스트를 다시 시작하고 만들었습니다. 인터넷 공유를 변경하기 전에 항상 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. 실제로, m_nIP 개인 변수 긴 데이터 형식의 IP (address.sin_addr.s_addr)를 저장합니다. 문제는 우리의 전체 제품 그룹이 IPv4를 사용하기 때문에 IPv6가 필요하지 않았다는 것입니다. 아래 코드를 사용하여이를 해결했습니다.

    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

정확히 당신이 문제가있는 무엇
+0

많은 모바일 네트워크는 IPv6 전용입니다. 나머지는 모두 이중 스택입니다. 모든 네트워크는 IPv4 트래픽을 작동시키기 위해 일부 NAT 변환을 때때로 두 번 이상 수행합니다. 연결의 양쪽 끝에서 IPv6을 지원하면 지연 시간이 줄어들고 성능이 향상됩니다. –