前几节我们一直用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提供了gethostbyname和gethostbyaddr函数。
同样为了获取服务的详细信息,linux提供了getservbyname和getservbyport函数。
这些函数返回的是hostent或servent结构体,对套接字编程来说不方便,因为套接字编程经常使用包含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;
}