我們需要先連接 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
客戶程序 做準備.