Unix网络编程学习笔记之第11章 名字与地址转换
一、 域名系統(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_errno1> 我們來(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");//error2. 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)題。
- 上一篇: cfa的pv怎么用计算机算,CFA考点解
- 下一篇: VSCode 远程开发:WLS 2 +