IPV4的C++代碼如何升遷到IPV6,以及如何優雅的進行IPV4,IPV6兼容性編碼

如果單純的從代碼角度來看,IPV4和IPV6的變化基本不大,主要是圍繞着sockaddr系列結構體的變化,API層面基本沒變化,變化最大的就是將字符串解析成地址結構體的那幾個函數,爲了解析字符串中的IPV4地址,IPV4使用 inet_addr()函數,在升級之後,更改爲inet_pton函數,這個函數除了地址字符串,還需要指定IP協議版本,另外,對域名的解析也有變化,IPV4使用gethostbyname()函數,而新的協議採用getaddrinfo()函數,只要處理好了這幾個函數,IPV4 V6混合編碼就基本OK了。

IPV4 IPV6混合編碼最重要的就是理清sockaddr系列結構體的關係,主要有這幾個結構體:

sockaddr   // 最基本的結構體,所有的API都是用這個結構體指針,用結構體長度來判斷使用哪個結構體;

sockaddr_in // IPV4使用的地址結構;

sockaddr_in6 // IPV6使用的地址結構;

sockaddr_storage  // 通用的地址存儲結構體,建議儘可能的用這個結構體來編碼;

這兒給出一個解析地址的函數實現:

 

bool CSocketUtility::DomainAnalyze(tagDomain & domain)
{
    bool result = false;
    if( !domain.host.empty() && domain.data.GetCapacity() )
    {
        if( IsIPV4Address( domain.host.c_str() ) )
        {
            if( domain.data.GetCapacity() >= sizeof(sockaddr_in) &&
                inet_pton( AF_INET, domain.host.c_str(), & (
                domain.data.Query<sockaddr_in>() -> sin_addr ) ) > 0 )
            {
                // return 1, succeed.
                domain.data.SetLength( sizeof(sockaddr_in) );
                domain.data.Query<sockaddr_in>() ->  sin_family  = AF_INET;
                domain.data.Query<sockaddr_in>() ->  sin_port     = htons( domain.port );
                result = true;
            }
        }
        else
        if( IsIPV6Address( domain.host.c_str() ) )
        {
            if( domain.data.GetCapacity() >= sizeof(sockaddr_in6) &&
                inet_pton( AF_INET6, domain.host.c_str(), & (
                domain.data.Query<sockaddr_in6>() -> sin6_addr ) ) > 0 )
            {
                // return 1, succeed.
                domain.data.SetLength( sizeof(sockaddr_in6) );
                domain.data.Query<sockaddr_in6>() -> sin6_family = AF_INET6;
                domain.data.Query<sockaddr_in6>() -> sin6_port     = htons( domain.port );
                result = true;
            }
        }
        else
        {
            addrinfo hints;
            memset( & hints, 0, sizeof( hints ) );
            hints.ai_family = AF_UNSPEC;

            addrinfo * retval = NULL;
            if( 0 == getaddrinfo( domain.host.c_str(), NULL, & hints, & retval ) )
            {
                // return 0, succeeded.
                if( retval )
                {
                    for( addrinfo * current = retval;
                        !result && current != NULL; current = current -> ai_next )
                    {
                        switch( current -> ai_family )
                        {
                        case AF_UNSPEC:
                            // nothing to do here;
                            break;
                        case AF_INET:
                            if( current -> ai_addrlen <= domain.data.GetCapacity() ) {
                                memcpy( domain.data, current -> ai_addr, current -> ai_addrlen );
                                domain.data.SetLength( current -> ai_addrlen );
                                domain.data.Query<sockaddr_in>()  -> sin_port  = htons( domain.port );
                                result = true;
                            }
                            break;
                        case AF_INET6:
                            if( current -> ai_addrlen <= domain.data.GetCapacity() ) {
                                memcpy( domain.data, current -> ai_addr, current -> ai_addrlen );
                                domain.data.SetLength( current -> ai_addrlen );
                                domain.data.Query<sockaddr_in6>() -> sin6_port = htons( domain.port );
                                result = true;
                            }
                            break;
                        }
                    }

                    freeaddrinfo( retval );
                    retval = NULL;
                }
            }
        }
    }
    return result;
}

 

其中,tagDomain就是地址的結構體,包含以下幾個數據:

    struct tagDomain
    {

        public:
            a_string host;

            U16 port;

            CMemory data;

            tagDomain();

            tagDomain(const tagDomain & value);

            virtual ~tagDomain();

            tagDomain & operator=(const tagDomain & value);

    };  //end struct tagDomain

其中,host是主機地址,port就是端口,而data,就是存儲的sockaddr_storage,結構體初始化時,需要爲data分配好內存;

在解析好地址之後,host, port, data內,都存儲好了數據,並在sockaddr中,存儲了地址所使用的協議版本,地址,端口,這個結構體如果是監聽器就必須一直保存,如果是連接器,在連接之後就可以丟棄了。接下來的代碼和原來幾乎一樣,只需做少量改變。

首先是創建SOCKET,需要根據協議版本創建對應的SOCKET類型。

SOCKET CTcpSelectModelImplement::Create(tagDomain & domain)
{
    SOCKET result = INVALID_SOCKET;
    if( domain.data.Alloc(
        sizeof(sockaddr_storage) ) &&
        CSocketUtility::DomainAnalyze(domain) )
    {
        result = socket(
            domain.data.Query<sockaddr>() -> sa_family,
            SOCK_STREAM, 0 );
    }
    return result;
}

注意這個函數,創建socket時用的是sockaddr結構體,無論sockaddr_in還是sockaddr_in6,結構體開頭的版本成員變量都是一樣的。可以用sockaddr這個結構體來取。

另外在開始的時候,解析地址之前,tagDomain 有一次內存的分配行爲,分配的大小時sockaddr_storage的大小。

創建socket之後,就是不同的流程,監聽器是綁定+監聽,而連接器就是連接主機:

 

bool CTcpSelectModelImplement::Listen(U64 name, const tagDomain & domain)
{
    bool result = false;
    if( name && !domain.host.empty() &&
        domain.port && domain.data.GetLength() )
    {
        CObjectPtr listener = Obtain( name );
        if( listener )
        {
            SOCKET hs = INVALID_SOCKET;

            CInterface<ITcpConnectionController> segment;
            if( segment.Mount(listener, IID_TCP_CONNECTION_CONTROLLER) ) {
                hs = segment -> GetSocket();
            }

            if( hs != INVALID_SOCKET )
            {
                // 綁定該地址上的該端口給hs。
                if( SOCKET_ERROR != ::bind(
                    hs, domain.data.Query<sockaddr>(), domain.data.GetLength() ) ) {
                    result = ( listen(hs, 5) != SOCKET_ERROR );
                }

                if( result )
                {
                    CCriticalSectionScope scope( region );
                    server.insert( make_pair(hs, name) );
                    server_update = true;
                }
            }
        }
    }
    return result;
}

可以注意到在bind函數中,使用的就是sockaddr結構體指針,並且通過結構體的長度來判斷採用那個協議版本。

連接函數如下:

bool CTcpSelectModelImplement::Connect(U64 name, const tagDomain & domain)
{
    bool result = false;
    if( name && domain.host.length() &&
        domain.port && domain.data.GetLength() )
    {
        CObjectPtr connector = Obtain( name );
        if( connector )
        {
            SOCKET hs = INVALID_SOCKET;

            CInterface<ITcpConnectionController> segment;
            if( segment.Mount(connector, IID_TCP_CONNECTION_CONTROLLER) ) {
                hs = segment -> GetSocket();
            }

            if( hs != INVALID_SOCKET )
            {
                // connect to domain;
                result = ( 0 == connect( hs,
                    domain.data.Query<sockaddr>(), domain.data.GetLength() ) );
                if( result )
                {
                    CCriticalSectionScope scope( region );
                    client.insert( make_pair(hs, name) );
                    client_update = true;
                }
            }
        }
    }
    return result;
}

這個處理與監聽函數基本一樣,而接受,發送消息的函數更是沒有什麼需要更改的地方。

到此,IPV4 IPV6的混合編碼基本就完成了,IPV4和IPV6最大的變化就是地址解析,而IO模型層面更是沒有什麼改變,沒有必要爲IPV4和IPV6分別寫一個類來實現不同的協議版本,只需要在原類上稍微修改一下結構即可。

 

另外,需要注意的是,對IOCP這個IO模型,使用的不是標準的socket函數,有些函數需要注意:

    result = ( AcceptEx( listener, hs, wb -> buf, 0,
        sizeof(sockaddr_storage), sizeof(sockaddr_storage), & bytes, ol ) == TRUE );

在AcceptEx函數中,原來使用的是 sizeof(sockaddr_in),爲了兼容IPV6,全部改爲sizeof(sockaddr_storage),否則使用IPV6的時候,會得到內存不足的錯誤。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章