青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

yehao's Blog

如何實現在一個 Socket 應用程序中同時支持 IPv4 和 IPv6

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

兩個要解決的問題

現代網絡中,IPv4, IPv6 共存的情況日益增加,而這兩種協議的地址格式,地址解析的 API 各不同,程序員必須面對如下兩個問題并且合理地解決這些問題。

  1. 怎么準確識別用戶輸入的地址或者域名是屬于 IPv4 網絡還是 IPv6 網絡? 

  2. 怎么屏蔽網絡連接細節,提供給用戶一個統一的接口? 

IPv4 與 IPv6 對比

目前我們使用的第二代互聯網 IPv4 技術,它的最大問題是網絡地址資源有限,IPv6 是“Internet Protocol Version 6”的縮寫,它是 IETF 設計的用于替代現行版本 IP 協議 -IPv4- 的下一代 IP 協議。與 IPV4 相比,IPv6 具有更大的地址空間。IPv4 中規定 IP 地址長度為 32 位;而 IPv6 中 IP 地址的長度為 128 位。

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


圖 1. IPv4 與 IPv6 區別
IPv4 與 IPv6 區別 

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


圖 2. getaddrinfo 函數說明
getaddrinfo 函數說明 

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


圖 3. getaddrinfo 參數說明
getaddrinfo 參數說明 

getaddrinfo 函數在 IPv6 和 IPv4 網絡下都能實現獨立于協議的名稱解析,而且它返回的指向 addrinfo 結構的鏈表中會存放所有由輸入參數 nodename 解析出的所有對應的 IP 信息,包括 IP 地址,協議族信息等。所以只要對 const struct addrinfo* hints 進行一些配置,就可以利用這個函數來識別連接目標的網絡協議屬性,進而根據其網絡協議族而進行準確的連接操作。這樣就解決了我們提出的第一個問題。具體的函數實現如下 清單 1所示:


清單 1. 使用 getaddrinfo 識別 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;   }   

回頁首

代碼結構設計

對于用戶來說,他們只想實現網絡連接,而并不希望了解太多網絡連接上冗繁的細節。如何屏蔽 IPv4 和 IPv6 網絡的差異性,讓用戶使用統一的函數接口來完成操作,就成為我們的第二個課題。 程序中申明了一個基類叫 BaseSocket,繼承于它的兩個子類 SocketV4 和 SocketV6 分別負責有關 IPv4、IPv6 網絡環境下的各種操作。詳見 圖 4


圖 4. 類繼承圖
類繼承圖 

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

  1. 把構造和析構函數隱藏起來(單件模式)

    在 BaseSocket 的函數申明中,通常作為類公共成員的構造函數和析構函數被塑造成了 protected 成員函數。而開放給用戶創建真正操作對象的函數卻是 CreateSmartSocket()。CreateSmartSocket() 函數會動態地根據網絡環境創建合適的子類 SocketV4 或者 SocketV6,使用的方法就是調用上文中提到的 getaddrinfo() 函數。生成的子類對象都存儲在靜態 smartSocketmap 中。這樣設計的原因是在于如果不這樣做,用戶就必須根據不同的網絡來創建屬于 IPv4 或者 IPv6 網絡的 socket 子類,然后分別調用他們的成員函數,這樣繁瑣又不利于用戶代碼的維護和擴展。smartSocketmap 以這樣的設計為用戶構建對象創作的一個統一的接口,在不同網絡下,只需要維護一套統一的代碼,而無需為不同網絡下的實現細節而費神。


  2. 向用戶提供代理對象

    在 BaseSocket 類中申明了兩個 BaseSocket 類型的指針,一個是 smartSocket,另外一個是 m_cursocket。BaseSocket* smartSocket 是一個靜態的全局代理指針,用戶只通過它來進行網絡操作。在客戶程序中,只存在一份由 CreateSmartSocket() 函數創建的 smartSocket 的副本,這是因為在每次需要網絡連接時網絡環境相應是固定的,不會由 v4 網絡突然轉變成 v6 網絡,一個副本在運行時已經滿足使用需求。CreateSmartSocket() 函數會先去偵測存儲空間堆上是否已經存在 smartSocket 指針,存在的話就會調用 DeleteSmartSocket() 刪除之前創建的副本,然后再創建一個新的 smartSocket 指針,提供給用戶使用。而 m_cursocket 是指向真正操作對象(子類)的指針。值得注意的是,m_cursocket 指針是隱藏在 BaseSocket 類中的,而 smartSocket 正是 BaseSocket 類為 m_cursocket 封裝的一層代理指針。用戶所知的僅僅是調用了 smartSocket 的某個成員函數,而實際上,程序通過把 m_cursocket 定位到 map smartSocketmap 中的某一項,獲得了真正的 SocketV4 或者 SocketV6 對象。



    圖 5. 對象結構圖
    對象結構圖 



    圖 6. 獲取 smartSocket 對象流程圖
    獲取 smartSocket 對象流程圖 

    圖 5和 圖 6就展示了程序如何根據用戶輸入的地址信息判斷網絡類型,繼而創建 smartSocket 對象的過程。


  3. 使用虛函數(實現多態性)

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



    圖 7. 利用 smartSocket 對象進行網絡連接流程圖
    利用 smartSocket 對象進行網絡連接流程圖 


圖 7描述了 m_cursocket 如何進行類型轉換,獲得準確的子類對象,并且調用子類 Connect 函數的過程。

綜上所述,通過以上三點,就可以降低用戶程序和網絡操作 Socket 部分的耦合性。讓用戶容易地實現他們所需要的網絡連接,而不必要太關注網絡環境的細節。同樣,因為耦合性降低,有關 Socket 代碼部分的更新和維護也相對方便,不會牽一發而動全身。


回頁首

OpenSSL 之 https 應用案例

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


具體的實現方法如下列程序片段所示
  1. 網絡連接

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



    清單 2. smartSocket 建立網絡連接
    						    /* 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

    這里是為了實現安全連接,對 openSSL 對象進行創建和初始化配置。如下 清單 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 的系統 fd 綁定到生成的 ssl 對象上,就可以在 HTTPsSocket 上加載 openSSL 協議了。最后使用 SSL_connect() 函數進行連接。如 清單 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. 接收數據

    利用先建立起的 smartSocket 連接進行數據接收。如 清單 5所示。



    清單 5. 接收數據
    						  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. 發送數據

    利用先建立起的 smartSocket 連接進行數據發送。如 清單 6所示。



    清單 6. 發送數據
    						  /*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;  			


回頁首

小結

本文介紹了一種屏蔽 socket 網絡連接細節的代碼設計,該方法可以很好地適應 IPv4 和 IPv6 的網絡環境,而且它提供給用戶一個統一的接口,把用戶從 v4 或者 v6 網絡的連接細節中解放出來。


參考資料

學習

獲得產品和技術

  • 下載 IBM 軟件試用版,體驗強大的 DB2®,Lotus®,Rational®,Tivoli®和 WebSphere®軟件。

討論

作者簡介

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

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

posted on 2013-11-18 15:55 厚積薄發 閱讀(1481) 評論(0)  編輯 收藏 引用 所屬分類: 網絡編程

導航

<2025年11月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

統計

常用鏈接

留言簿

隨筆分類

文章分類

文章檔案

搜索

最新評論

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            亚洲人永久免费| 国产精品初高中精品久久| 亚洲国产精品嫩草影院| 久久久久这里只有精品| 午夜视频在线观看一区| 亚洲欧美精品| 久久国产精品第一页| 久久综合九色99| 欧美大片免费看| 亚洲人成高清| 亚洲一区二区三区激情| 香蕉久久夜色精品国产| 免费黄网站欧美| 欧美色视频一区| 国产日韩欧美精品| 在线观看精品一区| 亚洲一区二区三区免费视频| 久久精品视频在线看| 亚洲盗摄视频| 午夜视频在线观看一区二区| 免费久久99精品国产自| 国产精品羞羞答答| 亚洲人在线视频| 久久久精彩视频| 亚洲美女中文字幕| 久久久久免费视频| 国产精品久久久久久超碰| 亚洲第一中文字幕在线观看| 亚洲激情自拍| 亚洲综合清纯丝袜自拍| 性做久久久久久久久| 免费不卡中文字幕视频| 亚洲免费观看视频| 久久精品国产第一区二区三区| 欧美日韩精品免费观看| 国内精品久久久久久久影视麻豆| 亚洲精品欧美极品| 久久婷婷国产麻豆91天堂| 一区二区三区精品久久久| 久久精品一区二区三区不卡牛牛 | 亚洲精品国久久99热| 欧美一级在线视频| 欧美日韩国产123区| 亚洲国产日韩欧美在线动漫| 欧美制服丝袜| 亚洲一区二区日本| 欧美日韩成人激情| 亚洲日韩欧美视频一区| 老牛嫩草一区二区三区日本| 亚洲欧美日韩国产一区| 国产精品嫩草99a| 亚洲在线免费视频| 亚洲精品国产品国语在线app | 猛干欧美女孩| 久久国产黑丝| 国户精品久久久久久久久久久不卡| 亚洲午夜视频在线| 亚洲区欧美区| 欧美v日韩v国产v| 伊大人香蕉综合8在线视| 久久精品中文字幕一区| 亚洲欧美成人一区二区三区| 国产精品日韩一区| 亚洲欧美精品在线| 亚洲视频在线一区| 国产精品电影网站| 亚洲影视九九影院在线观看| 9国产精品视频| 国产精品捆绑调教| 久久精品女人| 久久久精彩视频| **网站欧美大片在线观看| 欧美xx视频| 欧美高清视频一区二区三区在线观看| 亚洲精品综合精品自拍| 亚洲日韩视频| 欧美视频在线看| 欧美一区二区三区四区在线观看| 亚洲一区日韩在线| 国产亚洲综合在线| 另类亚洲自拍| 欧美国产日本高清在线| 亚洲日韩欧美一区二区在线| 麻豆久久婷婷| 欧美成人一区二区三区在线观看| 亚洲精品久久久久久久久久久| 欧美电影免费观看高清完整版| 欧美精品一区二区三区在线播放| 亚洲无限av看| 欧美一区二区三区在线免费观看| 在线观看久久av| 一区二区三区国产盗摄| 国产一区av在线| 亚洲品质自拍| 国产婷婷成人久久av免费高清| 久久综合色播五月| 欧美日韩一区二区三区在线 | 日韩一区二区免费看| 国产精品久久婷婷六月丁香| 免费观看成人鲁鲁鲁鲁鲁视频| 欧美日韩精品免费看| 久久精品欧美日韩| 欧美精品久久久久久久| 久久福利一区| 欧美日韩精品欧美日韩精品| 久久久青草青青国产亚洲免观| 欧美激情视频一区二区三区在线播放 | 在线视频日韩精品| 久久午夜影视| 午夜在线成人av| 欧美精品三级| 欧美 日韩 国产 一区| 国产精品久久久久毛片软件| 免费不卡在线观看| 国产欧美日韩精品在线| 亚洲日韩欧美一区二区在线| 亚洲大片在线| 欧美在线亚洲综合一区| 中文av一区二区| 欧美激情第三页| 另类春色校园亚洲| 国产有码一区二区| 亚洲欧美日韩精品久久亚洲区| 日韩一级免费| 免费欧美日韩| 蜜桃精品久久久久久久免费影院| 国产精品一区二区视频| 一本久久青青| 日韩视频免费大全中文字幕| 噜噜噜噜噜久久久久久91| 久久精品国产在热久久| 国产日产亚洲精品| 亚洲欧美激情一区| 亚洲欧美精品伊人久久| 欧美日韩国产免费| 亚洲精品久久久久久久久久久| 在线欧美福利| 欧美a一区二区| 亚洲国产精品久久久久秋霞不卡 | 亚洲黄色天堂| 在线播放日韩专区| 亚洲女人小视频在线观看| 国产精品99久久久久久www| 欧美激情一区二区在线| 亚洲国产裸拍裸体视频在线观看乱了中文| 一区二区视频免费在线观看| 久久免费国产精品1| 麻豆精品在线视频| 亚洲激情中文1区| 欧美日韩免费观看中文| 一本色道婷婷久久欧美| 亚洲欧美激情诱惑| 国产一区二区三区精品欧美日韩一区二区三区 | 欧美一区免费视频| 国产视频一区二区在线观看| 欧美一乱一性一交一视频| 美国十次了思思久久精品导航| 在线成人www免费观看视频| 免费成人在线观看视频| 日韩午夜免费| 久久天天躁狠狠躁夜夜爽蜜月| 国产综合久久久久影院| 欧美成人在线免费观看| 99在线热播精品免费| 久久蜜桃香蕉精品一区二区三区| 亚洲黄色在线视频| 欧美视频在线观看免费| 久久精品99久久香蕉国产色戒| 亚洲国产精品一区在线观看不卡| 亚洲一区三区视频在线观看| 永久久久久久| 欧美视频你懂的| 久久久999精品免费| 日韩视频一区二区在线观看 | 91久久精品一区二区三区| 亚洲视频在线二区| 国产主播在线一区| 欧美日韩国产欧美日美国产精品| 午夜激情亚洲| 亚洲国产高潮在线观看| 午夜精品国产| 亚洲激情在线观看| 国产亚洲女人久久久久毛片| 欧美久久综合| 久久伊人免费视频| 亚洲午夜视频在线| 亚洲黄色精品| 久久久噜噜噜久久狠狠50岁| 亚洲欧美成人一区二区在线电影| 在线免费不卡视频| 国产精品亚洲产品| 欧美日韩一视频区二区| 麻豆久久婷婷| 久久九九全国免费精品观看| 宅男噜噜噜66国产日韩在线观看| 欧美国产日韩一区二区在线观看| 久久国产精品黑丝| 亚洲一区在线观看视频| 99精品热视频只有精品10| 亚洲国产成人久久综合一区|