日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Unix网络编程学习笔记之第11章 名字与地址转换

發(fā)布時(shí)間:2023/12/20 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Unix网络编程学习笔记之第11章 名字与地址转换 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、 域名系統(tǒng)(DNS)

1. 簡(jiǎn)介

DNS主要用于主機(jī)名和IP地址之間的映射。

主機(jī)名可以是簡(jiǎn)單的名字ljm,也可以是全限定域名ljm.localdomainbaidu.com等。

?

2.資源記錄

DNS中的條目稱為資源記錄(RR)。我們感興趣的RR類(lèi)型只有幾個(gè):

A???????????? A記錄把一個(gè)主機(jī)名映射為一個(gè)32位的IPv4地址。

AAAA??? 4A記錄把一個(gè)主機(jī)名映射為一個(gè)128位的IPv6地址。

例如:

ljm???????????????IN????? A??? 127.0.0.1

????????????????????IN????? AAAA3ffe:1f8d:9bc3:1234:ef93:ac89

PTR??????? 稱為指針記錄。把IP地址映射為主機(jī)名。對(duì)IPv4地址,32位的4個(gè)字節(jié)先反轉(zhuǎn)順序,再添加in-addr.arpa。對(duì)于IPv6地址,128位地址每四位組先反轉(zhuǎn),再添加ip6.arpa。

例如上面主機(jī)ljm的兩個(gè)PTR記錄為:

1.0.0.127.in-addr.arpa和8.9.c.a.3.9.f.e….ip6.arpa

?

3. 解析器和名字服務(wù)器

1> 名字服務(wù)器。

每個(gè)組織機(jī)構(gòu)都會(huì)有一個(gè)名字服務(wù)器(有時(shí)也叫DNS服務(wù)器)。它們保存著主機(jī)名和IP地址之間的映射的資源記錄。

2> 解析器

客戶端和服務(wù)器端等應(yīng)用程序通過(guò)解析器的函數(shù)來(lái)對(duì)主機(jī)名或IP地址進(jìn)行解析。

典型的解析器函數(shù)為gethostbyname和gethostbyaddr。

3> 具體過(guò)程

?

當(dāng)我們需要解析某個(gè)主機(jī)名或IP地址時(shí),我們?cè)诖a中調(diào)用解析器函數(shù),則解析器函數(shù)就會(huì)根據(jù)解析器配置文件(/etc/resolv.conf)中的本地名字服務(wù)器的IP地址,去發(fā)出UDP查詢,如果找不到,則會(huì)在整個(gè)因特網(wǎng)上查詢其他名字服務(wù)器。如果答案太長(zhǎng),則本地解析器會(huì)自動(dòng)切換到TCP。

?

二、gethostbyname和gethostbyaddr函數(shù)

1. gethostbyname函數(shù)

從主機(jī)名到IPv4地址的映射。

該函數(shù)執(zhí)行的只是查詢A記錄,所以即使主機(jī)有IPv6地址,也不會(huì)返回。

#include <netdb.h> struct hostent* gethostbyname(const char* hostname); //返回:若成功返回非NULL指針,若出錯(cuò)返回NULL并設(shè)置h_errno

1> 我們來(lái)看看結(jié)構(gòu)體hostent:

struct hostent{char* h_name; //規(guī)范名字char** h_aliases; //主機(jī)別名int h_addrtype; //地址族:AF_INETint h_length; //地址長(zhǎng)度:4char** h_addr_list;//IPv4地址 };

規(guī)范名字,也可以理解為全限定域名。

主機(jī)別名,一個(gè)主機(jī)可能有多個(gè)主機(jī)別名,所以這里是二維char數(shù)組。

地址族和地址長(zhǎng)度,由于這里只能是IPv4的地址映射,所以這兩個(gè)值不會(huì)改變,既然不會(huì)改變,為何需要這兩個(gè)參數(shù)?感覺(jué)有點(diǎn)多余。

h_addr_list,主機(jī)可能會(huì)有多個(gè)IP地址,所以這里是二維數(shù)組。這里的IP地址顯然是二進(jìn)制型的,如要輸出也需轉(zhuǎn)換表達(dá)式型。注意這里是char型的二維數(shù)組。為何不是in_addr結(jié)構(gòu)的一維數(shù)組?不得而知。

2> 當(dāng)gethostbyname返回錯(cuò)誤時(shí):

這里當(dāng)函數(shù)發(fā)送錯(cuò)誤時(shí),不是設(shè)置errno,而是設(shè)置h_errno。一般我們使用函數(shù)hstrerror來(lái)解析這個(gè)h_errno錯(cuò)誤值。

const char* hstrerror(int h_errno);

3> 我們來(lái)寫(xiě)一個(gè)例子程序,輸入任意多個(gè)主機(jī)名,輸出每個(gè)主機(jī)名對(duì)應(yīng)的規(guī)范名字,別名,IP地址。

#include "unp.h" int main(int argc, char** argv) {char* ptr, ** pptr;char buff[4];struct hostent * hptr;while(--argc>0){ptr=*++argv;if((hptr=gethostbyname(ptr))==NULL){printf("error for host : %s : %s\n", ptr,hstrerror(h_errno));continue;}printf("host name : %s\n",ptr);for(pptr=hptr->h_aliases;*pptr!=NULL;pptr++)printf("aliases: %s\n",*pptr);switch(hptr->h_addrtype){case AF_INET:for(pptr=hptr->h_addr_list;*pptr!=NULL;pptr++)printf("aliases: %s\n",Inet_ntop(hptr->h_addrtype, *pptr, buff, sizeof(buff)));break;default:printf("unknown address type\n");break;}} }

2. gethostbyaddr函數(shù)。

與上面的函數(shù)作用相反,從一個(gè)二進(jìn)制的IPv4地址轉(zhuǎn)換到相應(yīng)的主機(jī)名。

#include <netdb.h> struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family);// len為4,family為AF_INET //返回:若成功返回非NULL指針,若出錯(cuò)返回NULL并設(shè)置h_errno

函數(shù)查詢的是in_addr.arpa消息記錄。

函數(shù)返回的是和gethostbyname返回一樣的結(jié)構(gòu)體,只是這里我們只關(guān)心結(jié)構(gòu)體中的h_name。

?

三、getservbyname和getservbyport函數(shù)

上面提到我們可以用主機(jī)名來(lái)代替IP地址,下面我們可以使用服務(wù)名來(lái)代替端口號(hào)。

0. 像主機(jī)一樣:可以使用主機(jī)名和IP地址來(lái)標(biāo)識(shí)一臺(tái)主機(jī)。

服務(wù):可以使用服務(wù)名和端口號(hào)來(lái)標(biāo)識(shí)一個(gè)服務(wù)。

一個(gè)服務(wù)可以支持多個(gè)協(xié)議(TCP,UDP,SCTP),就像一個(gè)進(jìn)程可以同時(shí)監(jiān)聽(tīng)TCP套接字和UDP套接字一樣。一般一個(gè)服務(wù)只有一個(gè)端口號(hào)如http的80

服務(wù)名和端口號(hào)的映射保存在一個(gè)文件(/etc/services)中。

這樣即使端口號(hào)發(fā)生變動(dòng),我們只需要修改文件(/etc/services)即可。而不需要重新編譯程序。

看一下/etc/services的內(nèi)容:

# service-name?port/protocol? [aliases ...]?? [# comment]

tcpmux?????????1/tcp?????????????????????????? #TCP port service multiplexer

tcpmux?????????1/udp?????????????????????????? # TCPport service multiplexer

http???????????80/tcp????????? www www-http??? # WorldWideWeb HTTP

http???????????80/udp????????? www www-http??? # HyperText Transfer Protocol

http???????????80/sctp???????????????????????? #HyperText Transfer Protocol

https??????????443/tcp???????????????????????? #http protocol over TLS/SSL

https??????????443/udp???????????????????????? #http protocol over TLS/SSL

https?????????? 443/sctp??????????????????????? # http protocol overTLS/SSL

1. getservbyname函數(shù)

從服務(wù)名映射到端口號(hào)。

struct servent* getservbyname(const char* servname, const char* protoname); //返回:如果成功返回非空指針,如果失敗返回NULL

此函數(shù)就是查找本地的/etc/services里面的內(nèi)容,上面可知,/etc/services每一行有三個(gè)東西,service-name,port,protocol。所以本函數(shù)提供了兩個(gè)參數(shù)來(lái)唯一標(biāo)識(shí)端口號(hào)。

函數(shù)返回的結(jié)構(gòu)體servent:

struct servent{char* s_name;char** s_aliases;int port;char* s_proto; };

這里我們只關(guān)系的是port,注意返回的是網(wǎng)絡(luò)字節(jié)序的,所以給sockaddr_in賦值時(shí),無(wú)需再去轉(zhuǎn)換字節(jié)序。

這里函數(shù)的第二個(gè)參數(shù)可以是NULL,此時(shí)返回哪個(gè)端口號(hào)取決于實(shí)現(xiàn),但一般無(wú)所謂,因?yàn)橐粋€(gè)服務(wù)通常不同的協(xié)議,通常對(duì)應(yīng)一個(gè)端口號(hào)。

如果指定protoname,則該協(xié)議必須支持。

ser=getservbyname("tcpmux","tcp");//ok ser=getservbyname("tcpmux","sctp");//error

2. getservbyport函數(shù)

從端口號(hào)到服務(wù)名的映射

struct servent* getservbyport(int port, const char* protoname); //返回:如果成功返回非空指針,如果失敗返回NULL

注意這里的port參數(shù)必須是網(wǎng)絡(luò)字節(jié)序的。

struct servent* ser; ser=getservbyport(htons(1),"tcp");//ok ser=getservbyport(htons(1),NULL);//ok ser=getservbyport(htons(1),"sctp");//error

?

之前的程序我們是用IP地址和端口號(hào)來(lái)標(biāo)識(shí)一個(gè)目標(biāo)主機(jī)上的進(jìn)程的。到目前為止,我們就可以使用主機(jī)名和服務(wù)名來(lái)標(biāo)識(shí)一個(gè)目標(biāo)主機(jī)上的進(jìn)程。

?

四、 我們把我們之前的獲取時(shí)間客戶端改為使用主機(jī)名和服務(wù)名來(lái)標(biāo)識(shí)。

#include "unp.h" #define MAXLINE 1024 #define PORT 13 void err_sys(const char* s) {fprintf(stderr, "%s\n", s);exit(1); } int main(int argc, char** argv) {int sockfd,nbytes;struct sockaddr_in servaddr;char buff[MAXLINE+1];char str[128];struct hostent* hp;struct in_addr** pptr;//注意這里是一維數(shù)組,每個(gè)元素指向一個(gè)結(jié)構(gòu)體。struct servent* ser;if(argc!=3)//輸入主機(jī)名和服務(wù)名err_sys("input error");hp=gethostbyname(argv[1]);if(hp==NULL){err_sys("wrong hostname");}pptr=(struct in_addr**)hp->h_addr_list;if((ser=getservbyname(argv[2],"tcp")==NULL))err_sys("wrong server name");for(;*pptr!=NULL;pptr++){if((sockfd=socket(AF_INET,SOCK_STREAM, 0))<0)continue;bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=ser->s_port;memcpy(&servaddr.sin_addr,*pptr,sizeof(struct in_addr));printf("trying %s\n",inet_ntop(AF_INET,(struct sockaddr*)&servaddr,str,sizeof(str)));if(connect(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr))==0)break;//successclose(sockfd);}if(*pptr==NULL)err_sys("unable to connect");while(nbytes=read(sockfd,buff,MAXLINE)>0){buff[nbytes]=0;fputs(buff,stdout);}exit(0); }

這里我們從命令行輸入?yún)?shù):服務(wù)器主機(jī)名和服務(wù)名

我們調(diào)用gethostbyname來(lái)獲得服務(wù)器IP地址列表,一個(gè)一個(gè)嘗試連接。如果連接失敗,要關(guān)閉套接字,然后重新socket,connect。不能直接重connect。

然后我們調(diào)用getservbyname來(lái)獲得端口號(hào),這里的端口號(hào)是眾所周知的端口號(hào),因?yàn)樵摵瘮?shù)是查看本機(jī)的/etc/services,來(lái)獲知端口號(hào)的。而服務(wù)器主機(jī)也是利用這個(gè)眾所周知的端口號(hào)服務(wù)名進(jìn)行bind的。

?

五、getaddrinfo函數(shù)

因?yàn)間ethostbyname和gethostbyaddr這兩個(gè)函數(shù)只適用于IPv4地址。所以有了既支持IPv4和IPv6的函數(shù)getaddrinfo。

getaddrinfo函數(shù)能夠處理IP地址和主機(jī)名的轉(zhuǎn)換,而且還能同時(shí)處理端口號(hào)和服務(wù)名的映射。

#include <netdb.h> int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo **result); //返回:成功返回0,失敗返回非0.

1. hostname為輸入的主機(jī)名,service為輸入服務(wù)名,hints為需要返回結(jié)果的提示信息,該信息會(huì)影響返回結(jié)果,可以是NULL。Result為函數(shù)返回的結(jié)果。

?

2. 說(shuō)一下為何返回結(jié)果result前有兩個(gè)*,該函數(shù)返回一個(gè)鏈表,*result指向鏈表的頭結(jié)構(gòu),注意此時(shí)我們是讓函數(shù)內(nèi)部開(kāi)辟內(nèi)存空間,調(diào)用函數(shù)者只需提供一個(gè)指針,在函數(shù)內(nèi)部修改這個(gè)指針本身,而不是要修改這個(gè)指針指向的東西。例如:

int main() {char * s1, * s2;getaddrinfo1(&s1);getaddrinfo2(s2); } void getaddrinfo1(char** s) {*s=new char[]; } void getaddrinfo2(char* s) {s=new char[]; }

顯然s1指向了一個(gè)內(nèi)部的合理空間,而s2經(jīng)過(guò)函數(shù)調(diào)用后,仍然屬于野指針。

?

3. 這里看一下結(jié)構(gòu)體addrinfo:

struct addrinfo{int ai_flags;//AI_PASSIVE, AI_CANONNAMEint ai_family;//AF_XXXint ai_socktype;//SOCK_XXXint ai_protocol;//0 or IPPROTO_XXXsocklen_tai_addrlen;//length of ai_addrchar* ai_canonname;//ptr to canonical name for hoststruct sockaddr* ai_addr;//ptr to socket addressstruct addrinfo* ai_next;//ptr to next struct in linked list };

這里前4個(gè)成員是給hints參數(shù)設(shè)置的。后4個(gè)成員是result返回的結(jié)果。因?yàn)榍?個(gè)成員的不同值會(huì)影響后4個(gè)成員的值。

1> ai_flags??? 一般的標(biāo)識(shí)值為AI_PASSIVE和AI_CANONNAME

AI_PASSIVE:套接字將用于被動(dòng)打開(kāi)。

AI_CANONNAME:告知函數(shù)返回主機(jī)的規(guī)范名字。即ai_canonname

2> ai_family??? 即返回的是主機(jī)名對(duì)應(yīng)的IPv4地址還是IPv6地址。

3> ai_socktype???? 即因?yàn)榉?wù)名可能對(duì)應(yīng)多個(gè)協(xié)議,所以這里指定返回TCP還是UDP

4> ai_protocol???? 指定具體的協(xié)議,當(dāng)ai_socktype不能唯一標(biāo)識(shí)特定的協(xié)議時(shí),就要用到此參數(shù)。即因?yàn)镾CTP也屬于流協(xié)議,其socktype也是SOCK_STREAM,所以如果某個(gè)服務(wù)支持TCP和SCTP,則我們就必須指明協(xié)議名。即:

IPPROTO_TCP或IPPROTO_UDP。

下面4個(gè)成員就不說(shuō)了,很清楚。

?

4. 如果hints為NULL,則ai_flags,ai_socktype,ai_protocol的值均為0,ai_family的值為AF_UNSPEC。

?

5. 該函數(shù)返回是一個(gè)鏈表,為何?

當(dāng)提供的主機(jī)名有多個(gè)IP地址時(shí),每個(gè)IP都會(huì)返回一個(gè)對(duì)應(yīng)的結(jié)構(gòu)。

當(dāng)未指明ai_socktype時(shí),提供的服務(wù)名支持多個(gè)協(xié)議,則每個(gè)協(xié)議返回一個(gè)結(jié)構(gòu)。

所以當(dāng)主機(jī)名有2個(gè)IP地址,服務(wù)名支持tcp和udp,且未指明ai_socktype,則就會(huì)返回2*2=4個(gè)結(jié)構(gòu)。

返回的鏈表的結(jié)構(gòu)體的順序是不固定。

?

6. 返回的結(jié)構(gòu)體中的ai_addr可直接用于socket,connect函數(shù)調(diào)用。因?yàn)槠銲P地址都是二進(jìn)制型的,且IP地址的類(lèi)型為sockaddr,和協(xié)議無(wú)關(guān)的。且IP地址和端口號(hào)都是網(wǎng)絡(luò)字節(jié)序的,所以無(wú)需任何轉(zhuǎn)換函數(shù)。

?

7. 函數(shù)getaddrinfo的一些常見(jiàn)的輸入

函數(shù)有六個(gè)可輸入的參數(shù)值:hostname,,service,以及hints的前4個(gè)成員。

(1) 客戶端

對(duì)于TCP和UDP客戶端而言,我們使用該函數(shù),用來(lái)創(chuàng)建連接,連接服務(wù)器的。所以我們需要指定hostname和service的值,而至于hints的4個(gè)成員,如果確認(rèn)知道自己處理的是哪一類(lèi)型套接字,應(yīng)該指定ai_socktype或和ai_protocol。一般ai_flags為AI_PASSIVE。而一般不指定ai_family,因?yàn)橹鳈C(jī)有可能用于IPv4和IPv6地址,我們需要一個(gè)一個(gè)嘗試socket->connect。

(2) 服務(wù)器端

對(duì)于服務(wù)器,我們使用該函數(shù),只是用來(lái)指定端口號(hào)的。所以我們一般不指定主機(jī)名,只指定service。而主機(jī)名為空,則返回的套接字地址中的IP地址為通配地址,也正是我們想要的。

注意如果不指定ai_family或指定為AF_UNSPEC,這時(shí)至少返回兩個(gè)結(jié)構(gòu),一個(gè)包含Ipv4的通配地址INADDR_ANY,另一個(gè)包含IPv6的通配地址IN6ADDR_ANY_INIT。這時(shí)我們可以使用select來(lái)監(jiān)聽(tīng)這兩個(gè)套接字。

對(duì)于hints的前4個(gè)成員,我們指定ai_flags為AI_PASSIVE。且應(yīng)該指定套接字的類(lèi)型,以防止返回多個(gè)結(jié)構(gòu)。

注意:因?yàn)槲覀兒罄m(xù)需要調(diào)用accept函數(shù)來(lái)獲得套接字,我們就需要先new一個(gè)套接字結(jié)構(gòu),這個(gè)套接字結(jié)構(gòu)的大小如何確定?

函數(shù)返回的鏈表中,addrinfo的每個(gè)結(jié)構(gòu)體中都會(huì)有其套接字結(jié)構(gòu)的大小ai_addrlen。所以我們根據(jù)這個(gè)值可以確認(rèn)套接字的大小。

?

8. 可以說(shuō),getaddrinfo函數(shù)功能很強(qiáng)大,但使用起來(lái)也很復(fù)雜。

?

六、gai_strerror函數(shù)

上文提到,getaddrinfo函數(shù),成功返回0,失敗返回非0的整數(shù).

而gai_strerror函數(shù)就是用來(lái)解釋失敗的錯(cuò)誤整數(shù)的。

const char* gai_strerror(int error); 典型的使用方法: int ret=getaddrinfo(...); if(ret!=0) {err_sys("%s\n",gai_strerror(ret)); }

七、freeaddrinfo函數(shù)

函數(shù)getaddrinfo函數(shù)返回一個(gè)結(jié)構(gòu)體鏈表,其所有的存儲(chǔ)空間都是動(dòng)態(tài)獲取的,如new或malloc。所以我們用完之后要釋放這些內(nèi)存。就是調(diào)用freeaddrinfo來(lái)釋放的。

void freeaddrinfo(struct addrinfo* ai);//典型的用法: struct addrinfo* result; intret=getaddrinfo(...,&result); freeaddrinfo(result);

這樣就可以釋放返回的整個(gè)鏈表。

但是這有個(gè)問(wèn)題,比如我們通過(guò)遍歷找到了我們所需的結(jié)構(gòu),然后把這個(gè)結(jié)構(gòu)體復(fù)制出來(lái),然后調(diào)用該函數(shù)釋放所有內(nèi)存。

但是:如果只復(fù)制這個(gè)結(jié)構(gòu)體本身的話,是有問(wèn)題的,因?yàn)檫@個(gè)結(jié)構(gòu)體內(nèi)有指針(套接字結(jié)構(gòu)指針和規(guī)范名字指針),復(fù)制的時(shí)候切記把這些指針指向的空間也要復(fù)制。不然只復(fù)制指針的話,則freeaddrinfo會(huì)釋放掉所有內(nèi)存,這樣我們復(fù)制出來(lái)的這個(gè)結(jié)構(gòu)體的指針指向的是已釋放的內(nèi)存,這樣很危險(xiǎn)。

所以復(fù)制結(jié)構(gòu)體時(shí),切記需要深度復(fù)制。

?

八、 一些實(shí)際的例子

1. 對(duì)于TCP客戶端:

提供確認(rèn)的主機(jī)名和服務(wù)名,然后對(duì)于服務(wù)器的每個(gè)IP地址,進(jìn)行:

while(){ socket->connect }

2. 對(duì)于TCP服務(wù)器端:

一般不提供主機(jī)名,而是提供服務(wù)名,然后對(duì)于本地的每個(gè)類(lèi)型的通配地址以及服務(wù)名,進(jìn)行:

while(){ socket->bind } accept() …

如果bind成功,則跳出循環(huán)。有點(diǎn)問(wèn)題,這樣只能綁定一個(gè)IP地址:或?yàn)镮Pv4的通配地址,或?yàn)镮Pv6的通配地址。

?

九、getnameinfo函數(shù)

該函數(shù)為getaddrinfo的互補(bǔ)函數(shù),即提供套接字地址,返回主機(jī)名和服務(wù)名。

<pre name="code" class="cpp">int getnameinfo(const structsockaddr* addr, socklen_t addrlen, char*hostname, socklen_t hostlen, char* serv,socklen_t servlen, int flags); //返回:成功返回0,失敗返回非0

各個(gè)參數(shù)都很明顯,只有最后一個(gè)flag,其用于指示一些東西。比如:

NI_DGRAM:指示返回的服務(wù)是基于UDP的,因?yàn)橛锌赡芏丝谔?hào)相同的不同協(xié)議對(duì)應(yīng)的是不同的服務(wù)。

其他的一些標(biāo)志值:


注意:這里可以把標(biāo)記值進(jìn)行或,然后這樣就可以同時(shí)設(shè)置兩個(gè)標(biāo)志值。

注意:getnameinfo和getaddrinfo都是設(shè)計(jì)DNS的,而一般服務(wù)器是不使用getnameinfo函數(shù)的,直接用IP地址來(lái)標(biāo)識(shí)就可以了,因?yàn)間etnameinfo設(shè)計(jì)DNS,其是很耗時(shí)的。

?

十、 可重入函數(shù)

1. 我們先來(lái)看看gethostbyname和gethostbyaddr的代碼:

static structhostent host; struct hostent* gethostbyname(const char* hostname) {/*.....*/return (&host); } struct hostent* gethostbyaddr(const char* addr,socklen_t len, int family) {/*.....*/return (&host); }

可以看到函數(shù)返回的都是一個(gè)static的對(duì)象。問(wèn)題就在這上面。

假如我們?cè)谝粋€(gè)主程序中調(diào)用gethostbyname函數(shù),且在信號(hào)處理函數(shù)中也調(diào)用gethostbyname,看看會(huì)發(fā)生什么:

main() {struct hostent* hp;...signal(SIGALRAM,sig_alrm);...hp=gethostbyname(...); } void sig_alrm(intsigno) {struct hostent* hp1;hp1=gethostbyname(...); }

假如此時(shí)主程序中執(zhí)行到函數(shù)gethostbyname期間,而且函數(shù)已經(jīng)處理好static的host對(duì)象了,準(zhǔn)備返回了,此時(shí)來(lái)了個(gè)信號(hào),則主程序中斷,去處理這個(gè)信號(hào),而在信號(hào)處理函數(shù)中重新調(diào)用了gethostbyname,那么該host對(duì)象將被重用,因?yàn)榇藭r(shí)只有一個(gè)進(jìn)程,只保留一個(gè)副本,這么一來(lái),原先由主程序計(jì)算出的值被重寫(xiě)成了信號(hào)處理函數(shù)調(diào)用計(jì)算出的值。則這樣就會(huì)產(chǎn)生錯(cuò)誤。

這樣就是不可重入函數(shù)。

?

2. 看看以前的函數(shù)可重入性:

1> gethostbyname、gethostbyaddr、getservbyname、getservbyport都是不可重入函數(shù)

2> inet_pton、inet_ntop都是可重入的。

3> getaddrinfo可重入的前提是調(diào)用的函數(shù)都是可重入的,比如函數(shù)內(nèi)調(diào)用的是gethostbyname可重入版本,getservbyname的可重入版本。

4> getnameinfo可重入的前提是調(diào)用的函數(shù)都是可重入的,比如函數(shù)內(nèi)調(diào)用的是gethostbyaddr可重入版本,getservbyport的可重入版本。

?

3. errno變量也有類(lèi)似的問(wèn)題。

首先,每個(gè)進(jìn)程有一個(gè)errno的副本。而在同一個(gè)進(jìn)程中,例如如下代碼:

if(close(fd)<0) {fprintf(stderr,"close error, errno= %s\n", errno); }

如果此時(shí)close函數(shù)產(chǎn)生錯(cuò)誤,則內(nèi)核設(shè)置errno,當(dāng)程序調(diào)用結(jié)束close時(shí),還未來(lái)得及執(zhí)行輸出時(shí),此時(shí)信號(hào)來(lái)了,信號(hào)處理函數(shù)中也產(chǎn)生了錯(cuò)誤,則errno被重置,則返回到主程序時(shí),就有問(wèn)題了。

?

4. 解決可重入性問(wèn)題的一個(gè)方法:就是在信號(hào)處理函數(shù)中永遠(yuǎn)不要調(diào)用不可重入函數(shù),且可把errno先進(jìn)行保存,然后在函數(shù)最后還原回來(lái)。如:

void sig_handler(int signo) {int errno_save=errno;/*...other code*/errno=errno_save; }

還有在信號(hào)處理函數(shù)中,不要調(diào)用標(biāo)準(zhǔn)I/O函數(shù),如fprintf等。因?yàn)楹芏喟姹緦?shí)現(xiàn)的標(biāo)準(zhǔn)I/O函數(shù)都是不可重入的。

?

5. gethostbyname_r和gethostbyaddr_r函數(shù)

首先,把不可重入函數(shù)改為可重入函數(shù)有兩種方法:

1> 不可重入函數(shù)的問(wèn)題在于返回一個(gè)全局static對(duì)象。而我們可以由調(diào)用者動(dòng)態(tài)開(kāi)辟一個(gè)對(duì)象空間,然后交由函數(shù)進(jìn)行修改。如gethostbyname,我們可以讓調(diào)用者先動(dòng)態(tài)開(kāi)辟一個(gè)hostent對(duì)象,然后讓該函數(shù)來(lái)進(jìn)行處理。

gethostbyname_r和gethostbyaddr_r函數(shù)就是這么做的。

但引入的問(wèn)題:

調(diào)用者不僅需要new一個(gè)hostent對(duì)象,還要提供hostent對(duì)象中的指針指向的空間。

struct hostent* gethostbyname_r(const char* hostname,struct hosten* result, char*buf, int buflen, int*h_errnop);

其中result就是交由函數(shù)修改的對(duì)象。而buf就是這個(gè)對(duì)象中的指針指向的一塊內(nèi)存空間。錯(cuò)誤碼為h_errnop,而不是全局變量h_errno

這里很難確認(rèn)這個(gè)buf的大小。不好用。

?

2> 我們可以讓不可重入函數(shù)本身new一個(gè)對(duì)象,最后返回。而不是返回一個(gè)全局static對(duì)象。getaddrinfo函數(shù)就是這么做的。

引入的問(wèn)題:該函數(shù)內(nèi)部分配的空間,必須需要調(diào)用者顯示釋放掉,即調(diào)用函數(shù)freeaddrinfo。否則造成內(nèi)存泄漏。

?

十一、 其他網(wǎng)絡(luò)信息

我們上面提到了gethostby…、getservby…。

網(wǎng)絡(luò)信息包含:主機(jī),服務(wù),網(wǎng)絡(luò),協(xié)議。因?yàn)槲覀冇?


其中主機(jī)和網(wǎng)絡(luò)是通過(guò)DNS來(lái)獲取的。

協(xié)議和服務(wù)是通過(guò)查詢本地主機(jī)的文件來(lái)獲取的。

?

?

?

?

?

?

?

?

?

?

?

?

?

?

總結(jié)

以上是生活随笔為你收集整理的Unix网络编程学习笔记之第11章 名字与地址转换的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。