《UNIX网络编程 卷1》 笔记: 名字与地址转换

前几节我们一直用IP地址表示主机,用端口号表示服务,与服务器通信必须要知道服务器的IP地址和端口号,这显然不方便。对于我们来说记住主机名和服务名更容易。

如下图所示:


在linux系统中,/etc/hosts文件保存了本地IP地址和主机名的映射关系,/etc/service文件保存了端口号和服务名之间的映射关系。

如果在/etc/hosts文件中找不到主机名对应的IP地址,则linux使用DNS协议向DNS服务器查询主机名对应的IP地址,DNS协议使用的传输层协议是UDP。我们来看一个例子,直接ping 百度的域名www.baidu.com,


我们看到www.baidu.com对应的一个IP地址是111.13.101.208。同时抓取UDP数据报,就可以看到DNS协议是怎样通信的。


为了获取主机的详细信息,linux提供了gethostbynamegethostbyaddr函数。

同样为了获取服务的详细信息,linux提供了getservbynamegetservbyport函数。

这些函数返回的是hostentservent结构体,对套接字编程来说不方便,因为套接字编程经常使用包含IP地址和端口号的sockaddr结构体。而且上面这些接口只支持IPv4,不支持IPv6。于是后来linux提供了getaddrinfo函数,它同时支持主机名到IP地址和服务名到端口号这两种转换,支持IPv4和IPv6,另外它返回的addrinfo结构体里面的成员直接可以当套接字API的参数使用,所以使用非常方便。

这里只给出getaddrinfo函数的原型和addrinfo结构体的定义,具体使用可参考下面的代码。

int getaddrinfo(const char *node, const char *service,
                const struct addrinfo *hints, 
                struct addrinfo **res);

struct addrinfo {
    int ai_flags;
    int ai_family;
    int ai_socktype;
    int ai_protocol;
    socklen_t ai_addrlen;
    struct sockaddr *ai_addr;
    char *ai_canonname;
    struct addrinfo *ai_next;
};

为了使我们的TCP和UDP程序支持主机名和服务名,我们定义了一些通用的函数。

tcp_connect函数: TCP客户执行的通常步骤,指定host和serv参数。

int tcp_connect(const char *host, const char *serv)
{
	int sockfd, n;
	struct addrinfo hints, *res, *ressave;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM; /*TCP*/

	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
		err_quit("tcp_connect error for %s, %s: %s", 
					host, serv, gai_strerror(n));
	ressave = res;
	do {
		/*创建TCP套接字*/
		sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (sockfd < 0)
			continue;
		/*连接到服务器,成功则跳出循环*/
		if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
			break;
		Close(sockfd);
	}while ((res = res->ai_next) != NULL);

	if (res == NULL)
		err_sys("tcp_connect error for %s, %s", host, serv);
	freeaddrinfo(ressave);

	return sockfd;
}

tcp_listen函数: TCP服务器执行的通常步骤,一般只指定serv参数。

int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
{
	int listenfd, n;
	const int on = 1;
	struct addrinfo hints, *res, *ressave;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM; /*TCP*/

	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
		err_quit("tcp_listen error for %s, %s: %s", 
					host, serv, gai_strerror(n));
	ressave = res;
	do {
		/*创建套接字*/
		listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (listenfd < 0)
			continue;
		/*设置SO_REUSEADDR选项*/
		Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
		/*绑定成功则跳出循环*/
		if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
			break;
		Close(listenfd);
	} while ((res = res->ai_next) != NULL);

	if (res == NULL)
		err_sys("tcp_listen error for %s, %s", host, serv);

	/**/
	Listen(listenfd, LISTENQ);

	if (addrlenp)
		*addrlenp = res->ai_addrlen;

	freeaddrinfo(ressave);

	return listenfd;
}
udp_client函数: UDP客户执行的通常步骤,不连接到服务器,指定host和serv参数。

int udp_client(const char *host, const char *serv, SA **saptr, socklen_t *lenp)
{
	int sockfd, n;
	struct addrinfo hints, *res, *ressave;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
		err_quit("udp_client error for %s, %s: %s", 
					host, serv, gai_strerror(n));
	ressave = res;
	do {
		sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		/*创建套接字成功*/
		if (sockfd >= 0)
			break;
	} while ((res = res->ai_next) != NULL);

	if (res == NULL)
		err_sys("udp_client error for %s, %s", host, serv);

	*saptr = Malloc(res->ai_addrlen);
	memcpy(*saptr, res->ai_addr, res->ai_addrlen);
	*lenp = res->ai_addrlen;

	freeaddrinfo(ressave);

	return sockfd;
}
udp_connect函数: UDP客户执行的通常步骤,连接到服务器,指定host和serv参数。

int udp_connect(const char *host, const char *serv)
{
	int sockfd, n;
	struct addrinfo hints, *res, *ressave;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
		err_quit("udp_connect error for %s, %s: %s", 
					host, serv, gai_strerror(n));
	ressave = res;
	do {
		sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (sockfd < 0)
			continue;
		if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
			break;
		Close(sockfd);
	} while ((res = res->ai_next) != NULL);

	if (res == NULL)
		err_sys("udp_connect error for %s, %s", host, serv);

	freeaddrinfo(ressave);

	return sockfd;
} 
udp_server函数: UDP服务器执行的通常步骤,一般只指定serv参数。

int udp_server(const char *host, const char *serv, socklen_t *addrlenp)
{
	int sockfd, n;
	struct addrinfo hints, *res, *ressave;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM; /*UDP*/

	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
		err_quit("udp_server error for %s, %s: %s", 
					host, serv, gai_strerror(n));
	ressave = res;
	do {
		/*创建套接字*/
		sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (sockfd < 0)
			continue;
		/*绑定成功则跳出循环*/
		if (bind(sockfd, res->ai_addr, res->ai_addrlen) == 0)
			break;
		Close(sockfd);
	} while ((res = res->ai_next) != NULL);

	if (res == NULL)
		err_sys("udp_server error for %s, %s", host, serv);

	if (addrlenp)
		*addrlenp = res->ai_addrlen;

	freeaddrinfo(ressave);

	return sockfd;
}
最后,由于getaddrinfo函数要传递addrinfo结构,使用不太方便,因此我们实现了一个 host_serv函数,它的代码如下:

struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype)
{
	int n;
	struct addrinfo hints, *res;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_CANONNAME; /*返回主机的规范名字*/
	hints.ai_family = family; /*AF_UNSPEC, AF_INET, AF_INET6 ...*/
	hints.ai_socktype = socktype; /*0, SOCK_STREAM, SOCK_DGRAM, ...*/

	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
		return NULL;

	return res;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值