欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UNP卷一chapter11 名字与地址转换

程序员文章站 2024-03-04 09:24:17
...

以下知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。

1、域名系统(DNS)

资源记录

A:A记录把一个主机名映射成一个32位的IPv4地址;

AAAA:把一个主机名映射成一个128位的IPv6;

PTR:PTR记录把IP地址映射成主机名;

MX:MX记录把一个主机指定作为给定主机的“邮件交换器”(mail exchanger)

CNAME:CNAME代表“canonical name”(规范名名),为常用的服务(例如ftp和www)指派CNAME记录。

解析器和名字服务器(关系如下)

UNP卷一chapter11 名字与地址转换

DNS的替代方法:静态主机文件(通常到/etc/hosts文件下,端口号则到/etc/services文件下查找)

2、名字与数值地址之间进行转换的函数

a、gethostbyname和gethostbyaddr在主机名字与IPv4地址之间进行转换(局限性便在于只适合IPv4网络协议)

#include<netdb.h>
struct hostent* gethostbyname(const char* hostname);//返回:若成功则为非这指针,若出错则为NULL且设置h_errno
struct hostent {
	char *h_name;//official (canoical) name of list(指向正式主机名\0)
	char** h_aliases;//pointer to array of pointers to alias names(指向具有多个元素为别名指针的数组,且以NULL结尾)
	int h_addrtype;//host address type: AF_INET(这也是gethostbyname具有局限性的地方,只适用于IPv4协议)
	int h_length;//length of address:4
	char** h_addr_list;//ptr to array of ptrs with IPv4 addrs
};

gethostbyname:当发生错误时,它不设置errno变量,而是将全局整数变量h_errrno设置为在头文件<netdb.h>中定义的下列常值之一:HOST_NOT_FOUND、TRY_AGAIN、NO_RECOVERY、NO_DATA(等同于NO_ADDRESS)

#include<netdb.h>
struct hostent* gethostbyaddr(const char *addr, socklen_t len, int family);//返回:若成功则为非空指针,若出错则为NULL且设置h_errno
使用此函数,返回的hostent结构中,通常感兴趣的字段为存放规范主机名的h_name。

b、getservbyname和getservbyport在服务名字和端口号之间进行转换(局限性同上)

#include<netdb.h>
struct servent* getservbyname(const char* servname, const char* protoname);//返回:若成功则为非空指针,若出错则为NULL
struct servent {
	char* s_name;//official service name
	char** s_aliases;//alias list
	int s_port;//port number, network byte order,此是调用getservbyname最感兴趣的值
	char* s_proto;//protocal to use
};
#include<netdb.h>
struct servent* getservbyport(int port, const char * protoname);//返回:若成功则为非空指针,若出错则为NULL.......port必须是网络字节序

使用gethostbyname和getservbyname的例子(很经典哈!)

#include	"unp.h"

int
main(int argc, char **argv)
{
	int					sockfd, n;
	char				recvline[MAXLINE + 1];
	struct sockaddr_in	servaddr;
	struct in_addr		**pptr;
	struct in_addr		*inetaddrp[2];//line29存在一个for循环检测,需要将第二个位置置NULL
	struct in_addr		inetaddr;
	struct hostent		*hp;
	struct servent		*sp;

	if (argc != 3)
		err_quit("usage: daytimetcpcli1 <hostname> <service>");
	//由主机名转换成IPv4
	if ( (hp = gethostbyname(argv[1])) == NULL) {//试着由主机名转换成IPv4,若转换失败则为NULL,否则执行else
		if (inet_aton(argv[1], &inetaddr) == 0) {//转换到网络字节序二进制值
			err_quit("hostname error for %s: %s", argv[1], hstrerror(h_errno));
		} else {
			inetaddrp[0] = &inetaddr;
			inetaddrp[1] = NULL;
			pptr = inetaddrp;
		}
	} else {
		pptr = (struct in_addr **) hp->h_addr_list;//获取IPv4地址
	}
	//由服务名转换成端口号
	if ( (sp = getservbyname(argv[2], "tcp")) == NULL)//试着由服务名转换成数值,若失败则为NULL
		err_quit("getservbyname error for %s", argv[2]);

	for ( ; *pptr != NULL; pptr++) {
		sockfd = Socket(AF_INET, SOCK_STREAM, 0);

		bzero(&servaddr, sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_port = sp->s_port;
		memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
		printf("trying %s\n",
			   Sock_ntop((SA *) &servaddr, sizeof(servaddr)));

		if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) == 0)//三次握手建立连接
			break;		/* success */
		err_ret("connect error");
		close(sockfd);//connect调用失败的描述符必须close,不能再用
	}
	if (*pptr == NULL)//当没有一个connect调用成功
		err_quit("unable to connect");

	while ( (n = Read(sockfd, recvline, MAXLINE)) > 0) {
		recvline[n] = 0;	/* null terminate */
		Fputs(recvline, stdout);
	}
	exit(0);
}

c、getaddrinfo和getnameinfo分别用于主机名字和IP地址之间以及服务名字和端口号之间的转换(好处就是无协议无关),由于getaddrinfo返回的所有存储空间都是动太获取的(如来自malloc调用),其存储空间需要通过调用freeaddrinfo返还给系统。

getaddrinfo函数不仅支持IPv4、IPv6,以及处理名字到地址以及服务名到端口号两种转换(此函数看作解释器,很妥当)

#include<netdb.h>
int getaddrinfo(const char *hostname, const char* service
				const struct addrinfo* hints, struct addrinfo** result);//返回:若成功则为0,若出错则为非0(不同非零值代表不同含义)
//hints顾名思义就是希望根据调用者按这个结构中填入关于期望返回的信息类型的暗示,其中的各值的如下所示
//ai_flags(零个或多个或在一起的AI_xxx值,如AI_NUMERICHOST | AI_NUMERICSERV)
//ai_family (某个AF_xxx)
//ai_socktype (某个SOCK_xxx值)
//ai_protocol (如IPPROTO_TCP、IPPROTO_UDP、0)
struct addrinfo {//very important very important very important
	int ai_flags;//AI_PASSIVE,AI_CANONNAME
	int ai_family;//AF_XXX for UNSPE、INET、INET6
	int ai_socktype;//SOCK_XXX
	int ai_protocal;//0 or IPPROTO_XXX for IPv4 and IPv6
	socklen_t ai_addrlen;//length of ai_addr
	char *ai_canonname;//ptr to canonocal name for host
	struct sockaddr* ai_addr;//ptr to socket address structure
	struct addrinfo* ai_next;//ptr to next structure in linked list
};

gai_strerror函数

#include<netdb.h>
//getaddrinfo返回的非0错误值的名字与含义,可通过gai_strerror以非0值作为它的唯一参数,返回一个指向对应的出错信息串的指针
const char * gai_strerror(int error);//返回:指向错误描述消息字符串的指针(常值说明见书上P250,图11-7)

freeaddrinfo函数

#include<netdb.h>
void freeaddrinfo(struct addrinfo* ai);
//浅复制:只复制这个addrinfo结构而不复制由它转而指向的其他结构称为浅复制
//深复制:既复制这个addrinfo结构又复制由它指向的所有其他结构

getnameinfo函数

#include<netdb.h>
int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen,
				char* host, socklen_t hostlen,
				char* serv, socklen_t servlen, int flags);//返回:若成功则为0,若出错则为非0
//其中flags值,可以为0,一个和多个,如NI_NUMERICHOST | NI_NUMERICSERV   以数串格式返回主机字符串和服务字符串形式(多个)

3、可重入函数

判断是否存在着覆写静态变量的值,如全局变量errno值,若存在,则为不可重入函数,若不存在,则为可重入。

因历史原因,gethostbyname、gethostbyaddr、getservbyname和getservport为不可重入函数,因为它们都返回指向同一个静态结构的指针。于是gethostbyname_t和gethostbyaddr_r函数被设计成可重入版本。

#include<netdb.h>
struct hostent* gethostbyname_r(const char* hostname, struct hostent* result,
	char* buf, int buflen, int *h_errnop);
struct hostent* gethostbyaddr_r(const char* addr, int len,int type,
	char* buf, int buflen, int *h_errnop);
//上述函数均返回:若成功则为非空指针,若出错则为NULL,具体的形参分析见书上P271

inet_pton、inet_ntop总是可重入的。

inet_ntoa是不可重入的,不过支持线程的一些实现提供了使用线程特定数据的可重入版本(目前对线程不太了解)。

getaddrinfo与getaddrname函数可重入的前提是由它调用的函数都是可重入的。具体分析,参见书上P269。