• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            yehao's Blog

            如何實(shí)現(xiàn)在一個(gè) Socket 應(yīng)用程序中同時(shí)支持 IPv4 和 IPv6

            簡介: 當(dāng)今的網(wǎng)絡(luò)主流是 IPv4 網(wǎng)絡(luò),但隨著 IP 地址的日益短缺,IPv6 網(wǎng)絡(luò)開始漸漸盛行,因此傳統(tǒng)的網(wǎng)絡(luò)編程也需要做一些改進(jìn)來適應(yīng) IPv6 和 IPv4 共存的網(wǎng)絡(luò)環(huán)境。 本文介紹了一種設(shè)計(jì)模式來根據(jù)用戶輸入的地址或者域名建立合適的網(wǎng)絡(luò)連接,并且屏蔽了網(wǎng)絡(luò)連接細(xì)節(jié),提供給用戶一個(gè)統(tǒng)一的接口進(jìn)行二次開發(fā)。 在文中還給出了一個(gè)基于 OpenSSL https 安全連接的應(yīng)用來說明該方法的使用細(xì)節(jié)。

            兩個(gè)要解決的問題

            現(xiàn)代網(wǎng)絡(luò)中,IPv4, IPv6 共存的情況日益增加,而這兩種協(xié)議的地址格式,地址解析的 API 各不同,程序員必須面對(duì)如下兩個(gè)問題并且合理地解決這些問題。

            1. 怎么準(zhǔn)確識(shí)別用戶輸入的地址或者域名是屬于 IPv4 網(wǎng)絡(luò)還是 IPv6 網(wǎng)絡(luò)? 

            2. 怎么屏蔽網(wǎng)絡(luò)連接細(xì)節(jié),提供給用戶一個(gè)統(tǒng)一的接口? 

            IPv4 與 IPv6 對(duì)比

            目前我們使用的第二代互聯(lián)網(wǎng) IPv4 技術(shù),它的最大問題是網(wǎng)絡(luò)地址資源有限,IPv6 是“Internet Protocol Version 6”的縮寫,它是 IETF 設(shè)計(jì)的用于替代現(xiàn)行版本 IP 協(xié)議 -IPv4- 的下一代 IP 協(xié)議。與 IPV4 相比,IPv6 具有更大的地址空間。IPv4 中規(guī)定 IP 地址長度為 32 位;而 IPv6 中 IP 地址的長度為 128 位。

            在 IPv4 網(wǎng)絡(luò)下,網(wǎng)絡(luò)編程主要依靠的是 socket 連接。在客戶端,其基本步驟如下,創(chuàng)建一個(gè) socket,使用 socket 連接服務(wù)器,最后通過 TCP 或者 UDP 協(xié)議進(jìn)行數(shù)據(jù)讀寫。如果把這套方法移植到 IPv6 網(wǎng)絡(luò)下,就需要在原來的基礎(chǔ)上引入新的協(xié)議族、新的數(shù)據(jù)結(jié)構(gòu)以及新的地址域名轉(zhuǎn)換函數(shù)等。具體的一些差異如 圖 1所示:


            圖 1. IPv4 與 IPv6 區(qū)別
            IPv4 與 IPv6 區(qū)別 

            在這里要稍微介紹下 getaddrinfo()函數(shù),它提供獨(dú)立于協(xié)議的名稱解析。函數(shù)的前兩個(gè)參數(shù)分別是節(jié)點(diǎn)名和服務(wù)名。節(jié)點(diǎn)名可以是主機(jī)名,也可以是地址串 (IPv4 的點(diǎn)分十進(jìn)制數(shù)表示或 IPv6 的十六進(jìn)制數(shù)字串 )。服務(wù)名可以是十進(jìn)制的端口號(hào),也可以是已定義的服務(wù)名稱,如 ftp、http 等。函數(shù)的第三個(gè)參數(shù) hints 是 addrinfo 結(jié)構(gòu)的指針,由調(diào)用者填寫關(guān)于它所想返回的信息類型的線索。函數(shù)的返回值是一個(gè)指向 addrinfo 結(jié)構(gòu)的鏈表指針 res。詳見 圖 2。


            圖 2. getaddrinfo 函數(shù)說明
            getaddrinfo 函數(shù)說明 

            getaddrinfo 函數(shù)之前通常需要對(duì)以下 6 個(gè)參數(shù)進(jìn)行以下設(shè)置:nodename、servname、hints 的 ai_flags、ai_family、ai_socktype、ai_protocol。在 6 項(xiàng)參數(shù)中,對(duì)函數(shù)影響最大的是 nodename,sername 和 hints.ai_flag。而 ai_family 只是有地址為 v4 地址或 v6 地址的區(qū)別。而 ai_protocol 一般是為 0 不作改動(dòng)。其中 ai_flags、ai_family、ai_socktype。說明如 圖 3所示:


            圖 3. getaddrinfo 參數(shù)說明
            getaddrinfo 參數(shù)說明 

            getaddrinfo 函數(shù)在 IPv6 和 IPv4 網(wǎng)絡(luò)下都能實(shí)現(xiàn)獨(dú)立于協(xié)議的名稱解析,而且它返回的指向 addrinfo 結(jié)構(gòu)的鏈表中會(huì)存放所有由輸入?yún)?shù) nodename 解析出的所有對(duì)應(yīng)的 IP 信息,包括 IP 地址,協(xié)議族信息等。所以只要對(duì) const struct addrinfo* hints 進(jìn)行一些配置,就可以利用這個(gè)函數(shù)來識(shí)別連接目標(biāo)的網(wǎng)絡(luò)協(xié)議屬性,進(jìn)而根據(jù)其網(wǎng)絡(luò)協(xié)議族而進(jìn)行準(zhǔn)確的連接操作。這樣就解決了我們提出的第一個(gè)問題。具體的函數(shù)實(shí)現(xiàn)如下 清單 1所示:


            清單 1. 使用 getaddrinfo 識(shí)別 IPv4 和 IPv6 
            				  BaseSocket* BaseSocket::CreateSmartSocket(char* ipaddr)   { 		     struct addrinfo *answer, hint, *curr;      bzero(&hint, sizeof(hint));      hint.ai_family = AF_UNSPEC;      hint.ai_socktype = SOCK_STREAM;      char ipstr2[128];      struct sockaddr_in  *sockaddr_ipv4;      struct sockaddr_in6 *sockaddr_ipv6;      	         int ret = getaddrinfo(ipaddr, NULL,&hint, &answer);      if (ret != 0) {          return NULL;      }      DeleteSmartSocket();       for (curr = answer; curr != NULL; curr = curr->ai_next) {          switch (curr->ai_family){              case AF_UNSPEC:                  //do something here                  break;              case AF_INET:                  sockaddr_ipv4 = reinterpret_cast<struct sockaddr_in *>( curr->ai_addr);                  inet_ntop(AF_INET, &sockaddr_ipv4->sin_addr, ipstr2,sizeof(ipstr2));                  smartSocketmap[typeIPv4]=new  SocketV4(ipstr2);                  break;              case AF_INET6:                  sockaddr_ipv6 = reinterpret_cast<struct sockaddr_in6 *>( curr->ai_addr);                  inet_ntop(AF_INET6, <sockaddr_ipv6->sin6_addr, ipstr2,sizeof(ipstr2));                  smartSocketmap[typeIPv6]=new  SocketV6(ipstr2);                  break;          }      } 	         freeaddrinfo(answer);      if(!smartSocketmap.empty())      {          smartSocket=new BaseSocket();      }       return smartSocket;   }   

            回頁首

            代碼結(jié)構(gòu)設(shè)計(jì)

            對(duì)于用戶來說,他們只想實(shí)現(xiàn)網(wǎng)絡(luò)連接,而并不希望了解太多網(wǎng)絡(luò)連接上冗繁的細(xì)節(jié)。如何屏蔽 IPv4 和 IPv6 網(wǎng)絡(luò)的差異性,讓用戶使用統(tǒng)一的函數(shù)接口來完成操作,就成為我們的第二個(gè)課題。 程序中申明了一個(gè)基類叫 BaseSocket,繼承于它的兩個(gè)子類 SocketV4 和 SocketV6 分別負(fù)責(zé)有關(guān) IPv4、IPv6 網(wǎng)絡(luò)環(huán)境下的各種操作。詳見 圖 4。


            圖 4. 類繼承圖
            類繼承圖 

            在設(shè)計(jì) BaseSocket 類的時(shí)候,并沒有把它作為一個(gè)單純的基類來使用,而是把它設(shè)計(jì)成了一個(gè) SocketV4 和 SocketV6 的代理類。我們都知道,C++ 支持向上類型轉(zhuǎn)換(取一個(gè)對(duì)象的地址,并將其作為基類的地址使用),結(jié)合虛函數(shù)能夠?qū)崿F(xiàn)多態(tài)性,我們就在這里使用一個(gè)基類的指針使其指向不同的子類實(shí)例,并把這些指針放到一個(gè)容器內(nèi)。這樣設(shè)計(jì)的初衷是希望外部使用者只使用類的公共接口,享受類的服務(wù),而無需關(guān)注類的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。具體來說,就是在 IPv4、IPv6 同時(shí)存在的網(wǎng)絡(luò)環(huán)境下,用戶只需要享用 BaseSocket 類提供出的公共服務(wù),而無需關(guān)注具體網(wǎng)絡(luò)環(huán)境下網(wǎng)絡(luò)操作的差異性。 為了達(dá)到上述的目的,BaseSocket 類的設(shè)計(jì)主要做了了以下幾點(diǎn)處理:

            1. 把構(gòu)造和析構(gòu)函數(shù)隱藏起來(單件模式)

              在 BaseSocket 的函數(shù)申明中,通常作為類公共成員的構(gòu)造函數(shù)和析構(gòu)函數(shù)被塑造成了 protected 成員函數(shù)。而開放給用戶創(chuàng)建真正操作對(duì)象的函數(shù)卻是 CreateSmartSocket()。CreateSmartSocket() 函數(shù)會(huì)動(dòng)態(tài)地根據(jù)網(wǎng)絡(luò)環(huán)境創(chuàng)建合適的子類 SocketV4 或者 SocketV6,使用的方法就是調(diào)用上文中提到的 getaddrinfo() 函數(shù)。生成的子類對(duì)象都存儲(chǔ)在靜態(tài) smartSocketmap 中。這樣設(shè)計(jì)的原因是在于如果不這樣做,用戶就必須根據(jù)不同的網(wǎng)絡(luò)來創(chuàng)建屬于 IPv4 或者 IPv6 網(wǎng)絡(luò)的 socket 子類,然后分別調(diào)用他們的成員函數(shù),這樣繁瑣又不利于用戶代碼的維護(hù)和擴(kuò)展。smartSocketmap 以這樣的設(shè)計(jì)為用戶構(gòu)建對(duì)象創(chuàng)作的一個(gè)統(tǒng)一的接口,在不同網(wǎng)絡(luò)下,只需要維護(hù)一套統(tǒng)一的代碼,而無需為不同網(wǎng)絡(luò)下的實(shí)現(xiàn)細(xì)節(jié)而費(fèi)神。


            2. 向用戶提供代理對(duì)象

              在 BaseSocket 類中申明了兩個(gè) BaseSocket 類型的指針,一個(gè)是 smartSocket,另外一個(gè)是 m_cursocket。BaseSocket* smartSocket 是一個(gè)靜態(tài)的全局代理指針,用戶只通過它來進(jìn)行網(wǎng)絡(luò)操作。在客戶程序中,只存在一份由 CreateSmartSocket() 函數(shù)創(chuàng)建的 smartSocket 的副本,這是因?yàn)樵诿看涡枰W(wǎng)絡(luò)連接時(shí)網(wǎng)絡(luò)環(huán)境相應(yīng)是固定的,不會(huì)由 v4 網(wǎng)絡(luò)突然轉(zhuǎn)變成 v6 網(wǎng)絡(luò),一個(gè)副本在運(yùn)行時(shí)已經(jīng)滿足使用需求。CreateSmartSocket() 函數(shù)會(huì)先去偵測(cè)存儲(chǔ)空間堆上是否已經(jīng)存在 smartSocket 指針,存在的話就會(huì)調(diào)用 DeleteSmartSocket() 刪除之前創(chuàng)建的副本,然后再創(chuàng)建一個(gè)新的 smartSocket 指針,提供給用戶使用。而 m_cursocket 是指向真正操作對(duì)象(子類)的指針。值得注意的是,m_cursocket 指針是隱藏在 BaseSocket 類中的,而 smartSocket 正是 BaseSocket 類為 m_cursocket 封裝的一層代理指針。用戶所知的僅僅是調(diào)用了 smartSocket 的某個(gè)成員函數(shù),而實(shí)際上,程序通過把 m_cursocket 定位到 map smartSocketmap 中的某一項(xiàng),獲得了真正的 SocketV4 或者 SocketV6 對(duì)象。



              圖 5. 對(duì)象結(jié)構(gòu)圖
              對(duì)象結(jié)構(gòu)圖 



              圖 6. 獲取 smartSocket 對(duì)象流程圖
              獲取 smartSocket 對(duì)象流程圖 

              圖 5和 圖 6就展示了程序如何根據(jù)用戶輸入的地址信息判斷網(wǎng)絡(luò)類型,繼而創(chuàng)建 smartSocket 對(duì)象的過程。


            3. 使用虛函數(shù)(實(shí)現(xiàn)多態(tài)性)

              在基類中,主要操作的函數(shù)都被申明為虛函數(shù)。如果編譯器發(fā)現(xiàn)一個(gè)類中有被聲明為 virtual 的函數(shù),就會(huì)為其生成一個(gè)虛函數(shù)表,也就是 VTABLE。VTABLE 實(shí)際上是一個(gè)函數(shù)指針的數(shù)組,每個(gè)虛函數(shù)占用這個(gè)數(shù)組的一個(gè)位置。派生類有自己的 VTABLE,但是派生類的 VTABLE 與基類的 VTABLE 有相同的函數(shù)排列順序,同名的虛函數(shù)被放在兩個(gè)數(shù)組的相同位置上。在創(chuàng)建類實(shí)例的時(shí)候,編譯器還會(huì)在每個(gè)實(shí)例的內(nèi)存布局中增加一個(gè) vptr 字段,該字段指向本類的 VTABLE。C++ 對(duì)于虛函數(shù)的調(diào)用采用晚捆綁,從而能夠?qū)崿F(xiàn)多態(tài)性。 在程序中,m_cursocket 雖然是一個(gè)基類指針,但它指向的卻是一個(gè)子類對(duì)象地址。由于這樣的轉(zhuǎn)換是子類向上轉(zhuǎn)換,所以是安全的。指向正確的子類對(duì)象后,如果需要調(diào)用成員函數(shù),就能通過本實(shí)例中的 vptr 指針指向本類的 VTABLE,由此獲得正確的子類成員函數(shù)的地址來進(jìn)行操作。



              圖 7. 利用 smartSocket 對(duì)象進(jìn)行網(wǎng)絡(luò)連接流程圖
              利用 smartSocket 對(duì)象進(jìn)行網(wǎng)絡(luò)連接流程圖 


            圖 7描述了 m_cursocket 如何進(jìn)行類型轉(zhuǎn)換,獲得準(zhǔn)確的子類對(duì)象,并且調(diào)用子類 Connect 函數(shù)的過程。

            綜上所述,通過以上三點(diǎn),就可以降低用戶程序和網(wǎng)絡(luò)操作 Socket 部分的耦合性。讓用戶容易地實(shí)現(xiàn)他們所需要的網(wǎng)絡(luò)連接,而不必要太關(guān)注網(wǎng)絡(luò)環(huán)境的細(xì)節(jié)。同樣,因?yàn)轳詈闲越档?,有關(guān) Socket 代碼部分的更新和維護(hù)也相對(duì)方便,不會(huì)牽一發(fā)而動(dòng)全身。


            回頁首

            OpenSSL 之 https 應(yīng)用案例

            下面我們展示一個(gè)網(wǎng)絡(luò)連接實(shí)例,在這個(gè)實(shí)例中,我們會(huì)使用到 SSL 連接。眾所周知,有些 server 或者網(wǎng)站會(huì)啟用 SSL 進(jìn)行安全連接,那么對(duì)于這一類的網(wǎng)絡(luò)連接就不是簡單的使用 socket 可以解決的,我們必須借用 OpenSSL 來幫助我們實(shí)現(xiàn)。通常我們的底層數(shù)據(jù)是用 OpenSSL 的 BIO 對(duì)象來處理的,借助 BIO_new_ssl(), BIO_new_accept() 等函數(shù)輕松實(shí)現(xiàn) IPv4 環(huán)境下的網(wǎng)絡(luò)安全連接。然而這些方法在 IPv6 的環(huán)境下卻沒有實(shí)現(xiàn)很好的支持。為此,我們需要另辟蹊徑來達(dá)成我們的目標(biāo)。經(jīng)過一段時(shí)間對(duì) OpenSSL 文檔的研究,我們發(fā)現(xiàn)以下方法既可以實(shí)現(xiàn)我們安全連接的目的,又可以同時(shí)支持 IPv4 和 IPv6 兩種網(wǎng)絡(luò)環(huán)境,具有比較好的可擴(kuò)展性。這個(gè)方法十分簡單,那就是手工創(chuàng)建一個(gè) socket,該 socket 連接了一個(gè) IPv6 或者 IPv4 地址,然后將 socket 綁定到某個(gè) SSL 對(duì)象上就可以實(shí)現(xiàn) SSL 的連接了。


            具體的實(shí)現(xiàn)方法如下列程序片段所示
            1. 網(wǎng)絡(luò)連接

              這個(gè)部分首先調(diào)用 BaseSocket::GetSmartSocket()創(chuàng)建了一個(gè) HTTPsSocket,HTTPsSocket 得到的就是代理指針 smartSocket 的地址,然后試圖調(diào)用 HTTPsSocket->sockConnect() 連接 SSL 端口。從調(diào)用過程可見用戶根本不用關(guān)心他現(xiàn)在所處的網(wǎng)絡(luò)是 IPv4 還是 IPv6 網(wǎng)絡(luò),只要調(diào)用統(tǒng)一的函數(shù)接口就可以了。如 清單 2所示。



              清單 2. smartSocket 建立網(wǎng)絡(luò)連接
              						    /* Create a HTTPsSocket */   if((HTTPsSocket=BaseSocket::GetSmartSocket())==NULL)      HTTPsSocket =BaseSocket::CreateSmartSocket(const_cast<char *>(SPName.c_str()));   if(HTTPsSocket==NULL)   {      return -1;   }    /* Connect the HTTPsSocket to SSLport */   if (HTTPsSocket->isConnected())   {      HTTPsSocket->disconnect();   }   if(HTTPsSocket->sockConnect(SSLport))   {      return SOCKETERR;   }   // END OF CONNECT TO THE SSLport  			


            2. 初始化 SSL

              這里是為了實(shí)現(xiàn)安全連接,對(duì) openSSL 對(duì)象進(jìn)行創(chuàng)建和初始化配置。如下 清單 3所示。



              清單 3. 初始化 OpenSSL
              						  /* Lets get nice error messages */   SSL_load_error_strings();    /* Setup all the global SSL stuff */   SSL_library_init(); 	  ctx=SSL_CTX_new(SSLv23_client_method());   if(ctx==NULL)   {      return -1;   }    /* Lets make a SSL structure */   if (!(ssl = SSL_new(ctx)))   {      return -1;   }  			


            3. 綁定 smartSocket 到 SSL

              通過把 smartSocket 的系統(tǒng) fd 綁定到生成的 ssl 對(duì)象上,就可以在 HTTPsSocket 上加載 openSSL 協(xié)議了。最后使用 SSL_connect() 函數(shù)進(jìn)行連接。如 清單 4所示。



              清單 4. 綁定 smartSocket 到 SSL
              						  /* Lets bind socket fd to the SSL structure */   int fd=HTTPsSocket->getSocketFD();   SSL_set_fd(ssl, fd);    /* Lets use a SSL connect */   if (SSL_connect (ssl) <= 0 )   {      return -1;   }  			


            4. 接收數(shù)據(jù)

              利用先建立起的 smartSocket 連接進(jìn)行數(shù)據(jù)接收。如 清單 5所示。



              清單 5. 接收數(shù)據(jù)
              						  int each = 0; 	  /*If it is openSSL enabled connection, then use SSL_read to get the message,   otherwise use  default HTTPSocket->sockRecv() function*/ 	  if(SSLenabled)   { 	     each = SSL_read(ssl, buff, len );   }   else   {      each = HTTPSocket->sockRecv((unsigned char *)buff, len, 0 );   }   return each; 	 			


            5. 發(fā)送數(shù)據(jù)

              利用先建立起的 smartSocket 連接進(jìn)行數(shù)據(jù)發(fā)送。如 清單 6所示。



              清單 6. 發(fā)送數(shù)據(jù)
              						  /*If it is openSSL enabled connection, then use SSL_write to send the message,   otherwise use  default HTTPSocket->sockSend() function*/ 	  int trans = 0, each = 0;   if(SSLenabled)   {      while (trans < len)      {          if((each = SSL_write(ssl, buf + trans, len - trans)) == 0)          {              return SOCKETERR ;          }          trans += each ;      }   }   else   {      while (trans < len)      {          if((each=HTTPSocket->sockSend((unsigned char *)buf+trans,len-trans,0))==0)          {              return SOCKETERR ;          }          trans += each ;      }   }   return trans;  			


            回頁首

            小結(jié)

            本文介紹了一種屏蔽 socket 網(wǎng)絡(luò)連接細(xì)節(jié)的代碼設(shè)計(jì),該方法可以很好地適應(yīng) IPv4 和 IPv6 的網(wǎng)絡(luò)環(huán)境,而且它提供給用戶一個(gè)統(tǒng)一的接口,把用戶從 v4 或者 v6 網(wǎng)絡(luò)的連接細(xì)節(jié)中解放出來。


            參考資料

            學(xué)習(xí)

            獲得產(chǎn)品和技術(shù)

            • 下載 IBM 軟件試用版,體驗(yàn)強(qiáng)大的 DB2®,Lotus®,Rational®,Tivoli®和 WebSphere®軟件。

            討論

            作者簡介

            陳魯,2010 年 4 月加入 IBM CSTL,熟悉 C/C++、XML、Windows/Linux makefile。

            孫妍,2009 年 3 月加入 IBM CSTL,一直從事 preboot DSA 的 GUI 界面的開發(fā)工作。她熟悉 C/C++、JavaScript、HTML 和 Dojo。

            posted on 2013-11-18 15:55 厚積薄發(fā) 閱讀(1387) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 網(wǎng)絡(luò)編程

            導(dǎo)航

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            統(tǒng)計(jì)

            常用鏈接

            留言簿

            隨筆分類

            文章分類

            文章檔案

            搜索

            最新評(píng)論

            国产精品久久亚洲不卡动漫| 久久亚洲国产午夜精品理论片| 久久婷婷是五月综合色狠狠| 亚洲色婷婷综合久久| 久久婷婷成人综合色综合| 88久久精品无码一区二区毛片| 亚洲精品无码久久毛片| 久久人人爽人人爽人人AV | 亚洲乱码精品久久久久..| 97久久超碰国产精品旧版| 欧美久久一级内射wwwwww.| 久久久久久久97| 久久av免费天堂小草播放| 亚洲欧美日韩中文久久| 国内精品久久久久久不卡影院| 亚洲国产精品久久电影欧美| 国产免费福利体检区久久| 少妇久久久久久久久久| 久久精品国产99久久久香蕉| 91精品国产91久久综合| 精品久久久无码21p发布 | 777午夜精品久久av蜜臀 | AV无码久久久久不卡网站下载| 久久丝袜精品中文字幕| 久久国产高清字幕中文| 久久天天躁狠狠躁夜夜不卡| 久久精品国产精品亚洲艾草网美妙| 99久久精品毛片免费播放| 久久久久久久久久久久久久| 久久精品这里只有精99品| 久久91综合国产91久久精品| 国产精品美女久久久m| 久久国产乱子伦免费精品| 国产成人精品综合久久久久| 伊人热热久久原色播放www| 久久综合狠狠综合久久97色| 久久国产香蕉一区精品| 国产精品成人99久久久久| 国内精品久久久久久久久电影网| 亚洲国产精品久久久久久| 国产激情久久久久影院老熟女免费 |