我们需要先连接 getaddrinfo
, 而 getaddrinfo
的特性实在太多, 本节也仅仅只是简单的做一个小结, 如果想要了解更加全面可以参考 unp 第245页.
getaddrinfo 函数
getaddrinfo
函数是与协议无关 (TCP, UDP都可以) 并且不仅支持 IPV4 还支持 IPV6 .
函数原型 :
#include <netdb.h>
int getaddrinfo(const char *hostname, const char *service,
const struct addrinfo *hints, struct addrinfo **result);
int gai_strerror(int error);
void freeaddrinfo(struct addrinfo *);
struct addrinfo{
int ai_flags;
int ai_family; // AF_XXXX
int ai_socktype; // SOCK_XXXX
int ai_protocol;
socklen_t ai_addrlen; // sizeof(ai_addr)
char * ai_cannoname;
struct sockaddr * ai_addr;
struct addrinfo * ai_next;
};
成功 : 返回0
失败 : 返回非 0 并设置错误码由gai_strerror()
输出
参数 :
- hostname : 主机名 (或者地址串)
- service : 服务名 (或者IP地址)
- hints : 返回的参数 (可以为 NULL)
- ai_flags : AI_CANONNAME(返回主机规范名字), **AI_PASSIVE(服务端被动监听)**等.
- ai_family : 可以设置返回的套接字类型
- result : 服务有多个套接字类型(链表)
因为 getaddrinfo
内部会申请内存, 所以最后需要调用 freeaddrinfo
函数释放内存.
host_serv 函数
通过封装 getaddrinfo
函数保证用户不用分配和释放 addrinfo
内存.
完整代码 : host_serv.c
#include "client_web.h"
// 返回给定主机名(或地址串) 的基本信息
struct addrinfo* host_serv(const char *hostname, const char *service,
int family, int socktype){
int n;
struct addrinfo hint, *res;
bzero(&hint, sizeof(hint)); // 书中并没有清零, 这步不能省略
hint.ai_flags = AI_CANONNAME;
hint.ai_family = family;
hint.ai_socktype = socktype;
if((n = getaddrinfo(hostname, service, &hint, &res)) != 0){
gai_strerror(n);
return NULL;
}
return res;
}
tcp_connect 函数
接下来我们通过 getaddrinfo
函数来封装 connect
函数, 保证能与域名的其中一个地址建立连接.
完整代码 : tcp_connect.c
#include "tcp_connect.h"
// 遍历主机的所有 IP 地址, 直到建立一个 TCP 连接
int tcp_connect(const char *hostname, const char *service){
int sockfd, n;
struct addrinfo hint, *res, *resave;
bzero(&hint, sizeof(hint));
hint.ai_family = AF_INET;
hint.ai_socktype = SOCK_STREAM;
if((n = getaddrinfo(hostname, service, &hint, &res)) != 0){
gai_strerror(n);
exit(1);
}
resave = res;
// 直到建立连接 或 遍历结束退出
do{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(socket < 0)
continue;
if(connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break;
close(sockfd);
}while((res = res->ai_next) != NULL);
if(res == NULL){
fprintf(stderr, "tcp_connect error for %s, %s", hostname, service);
exit(1);
}
freeaddrinfo(resave);
return sockfd;
}
小结
本节只是简单的介绍了 getaddrinfo 函数, 并且封装了两个函数准备之后完成 web
客户程序 做准备.