struct sockaddr_in6 : 代表的是 IPv6 的地址信息struct addrinfo : 這是一個通用的結構體,里面可以存儲 IPv4 或 IPv6 類型地址的信息getaddrinfo : 這是一個十分方便的接口,在上述 UDP 程序中許多手動填寫的部分,都能夠省去,有該函數替我們完成改寫一下上方的例子:
接收端:
int sock; /* 套接字 */
socklen_t addr_len; /* 發(fā)送端的地址長度,用于 recvfrom */
char mess[15];
char get_mess[GET_MAX]; /* 后續(xù)版本使用 */
struct sockaddr_in host_v4; /* IPv4 地址 */
struct sockaddr_in6 host_v6; /* IPv6 地址 */
struct addrinfo easy_to_use; /* 用于設定要獲取的信息以及如何獲取信息 */
struct addrinfo *result; /* 用于存儲得到的信息(需要注意內存泄露) */
struct addrinfo * p;
/* 準備信息 */
memset(&easy_to_use, 0, sizeof easy_to_use);
easy_to_use.ai_family = AF_UNSPEC; /* 告訴接口,我現在還不知道地址類型 */
easy_to_use.ai_flags = AI_PASSIVE; /* 告訴接口,稍后“你”幫我填寫我沒明確指定的信息 */
easy_to_use.ai_socktype = SOCK_DGRAM; /* UDP 的套接字 */
/* 其余位都為 0 */
/* 使用 getaddrinfo 接口 */
getaddrinfo(NULL, argv[1], &easy_to_use, &result); /* argv[1] 中存放字符串形式的 端口號 */
/* 創(chuàng)建套接字,此處會產生兩種寫法,但更保險,可靠的寫法是如此 */
/* 舊式方法
* sock = socket(PF_INET, SOCK_DGRAM, 0);
*/
/* 把IP 和 端口號信息綁定在套接字上 */
/* 舊式方法
* memset(&recv_host, 0, sizeof(recv_host));
* recv_host.sin_family = AF_INET;
* recv_host.sin_addr.s_addr = htonl(INADDR_ANY);/* 接收任意的IP */
* recv_host.sin_port = htons(6000); /* 使用6000 端口號 */
* bind(sock, (struct sockaddr *)&recv_host, sizeof(recv_host));
*/
for(p = result; p != NULL; p = p->ai_next) /* 該語法需要開啟 -std=gnu99 標準*/
{
sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if(sock == -1)
continue;
if(bind(sock, p->ai_addr, p->ai_addrlen) == -1)
{
close(sock);
continue;
}
break; /* 如果能執(zhí)行到此,證明建立套接字成功,套接字綁定成功,故不必再嘗試。 */
}
/* 進入接收信息的狀態(tài) */
//recvfrom(sock, mess, 15, 0, (struct sockaddr *)&send_host, &addr_len);
switch(p->ai_socktype)
{
case AF_INET :
addr_len = sizeof host_v4;
recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_v4, &addr_len);
break;
case AF_INET6:
addr_len = sizeof host_v6
recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_v6, &addr_len);
break;
default:
break;
}
freeaddrinfo(result); /* 釋放這個空間,由getaddrinfo分配的 */
/* 接收完成,關閉套接字 */
close(sock);
代碼解釋:
首先解釋幾個新的結構體
struct addrinfo 這個結構體的內部順序對于 *nix 和 Windows 稍有不同,以 *nix 為例
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; /* 一個域名/IP地址可能解析出多個不同的 IP */
};
ai_family 如果設定為 AF_UNSPEC 那么在調用 getaddrinfo 時,會自動幫你確定,傳入的地址是什么類型的ai_flags 如果設定為 AI_PASSIVE 那么調用 getaddrinfo 且向其第一個參數傳入 NULL 時會自動綁定自身 IP,相當于設定 INADDR_ANYai_socktype 就是要創(chuàng)建的套接字類型,這個必須明確聲明,系統(tǒng)沒法預判(日后人工智能說不定呢?)ai_protocol 一般情況下我們設置為 0,含義可以自行查找,例如 MSDN 或者 UNPai_addr 這里保存著結果,可以通過 調用getaddrinfo之后 的第四個參數獲得。ai_addrlen 同上ai_next 同上getaddrinfo 強大的接口函數
int getaddrinfo(const char * node, const char * service,
const struct addrinfo * hints, struct addrinfo ** res);
node 便是待獲取或者待綁定的 域名 或是 IP,也就是說,這里可以直接填寫域名,由操作系統(tǒng)來轉換成 IP 信息,或者直接填寫IP亦可,是以字符串的形式service 便是端口號的意思,也是字符串形式hints 通俗的來說就是告訴接口,我需要你反饋哪些信息給我(第四個參數),并將這些信息填寫到第四個參數里。res 便是保存結果的地方,需要注意的是,這個結果在API內部是動態(tài)分配內存了,所以使用完之后需要調用另一個接口(freeaddrinfo)將其釋放實際上對于現代的 套接字編程 而言,多了幾個新的存儲 IP 信息的結構體,例如 struct sockaddr_in6 和 struct sockaddr_storage 等。
其中,前者是后者的大小上的子集,即一個 struct storage 一定能夠裝下一個 struct sockaddr_in6,具體(實際上根本看不到有意義的實現)
struct sockaddr_in6{
u_int16_t sin6_family;
u_int16_t sin6_port;
u_int32_t sin6_flowinfo; /* 暫時忽略它 */
struct in6_addr sin6_addr; /* IPv6 的地址存放在此結構體中 */
u_int32_t sin_scope_id; /* 暫時忽略它 */
};
struct in6_addr{
unsigned char s6_addr[16];
}
------------------------------------------------------------
struct sockaddr_storage{
sa_family_t ss_family; /* 地址的種類 */
char __ss_pad1[_SS_PAD1SIZE]; /* 從此處開始,不是實現者幾乎是沒辦法理解 */
int64_t __ss_align; /* 從名字上可以看出大概是為了兼容兩個不同 IP 類型而做出的妥協(xié) */
char __ss_pad2[_SS_PAD2SIZE]; /* 隱藏了實際內容,除了 IP 的種類以外,無法直接獲取其他的任何信息。 */
/* 在各個*nix 的具體實現中, 可能有不同的實現,例如 `__ss_pad1` , `__ss_pad2` , 可能合并成一個 `pad` 。 */
};
在實際中,我們往往不需要為不同的IP類型聲明不同的存儲類型,直接使用 struct sockaddr_storage 就可以,使用時直接強制轉換類型即可
改寫上方 接收端 例子中,進入接收信息的狀態(tài)部分
/* 首先將多于的變量化簡 */
// - struct sockaddr_in host_v4; /* IPv4 地址 */
// - struct sockaddr_in6 host_v6; /* IPv6 地址
struct sockaddr_storage host_ver_any; /* + 任意類型的 IP 地址 */
...
/* 進入接收信息的狀態(tài)部分 */
recvfrom(sock, mess, 15, 0, (struct sockaddr *)&host_ver_any, &addr_len); /* 像是又回到了只有 IPv4 的年代*/
補充完整上方對應的 發(fā)送端 代碼
int sock;
const char* mess = "Hello Server!";
char get_mess[GET_MAX]; /* 后續(xù)版本使用 */
struct sockaddr_storage recv_host; /* - struct sockaddr_in recv_host; */
struct addrinfo tmp, *result;
struct addrinfo *p;
socklen_t addr_len;
/* 獲取對端的信息 */
memset(&tmp, 0, sizeof tmp);
tmp.ai_family = AF_UNSPEC;
tmp.ai_flags = AI_PASSIVE;
tmp.ai_socktype = SOCK_DGRAM;
getaddrinfo(argv[1], argv[2], &tmp, &result); /* argv[1] 代表對端的 IP地址, argv[2] 代表對端的 端口號 */
/* 創(chuàng)建套接字 */
for(p = result; p != NULL; p = p->ai_next)
{
sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); /* - sock = socket(PF_INET, SOCK_DGRAM, 0); */
if(sock == -1)
continue;
/* 此處少了綁定 bind 函數,因為作為發(fā)送端不需要講對端的信息 綁定 到創(chuàng)建的套接字上。 */
break; /* 找到就可以退出了,當然也有可能沒找到,那么此時 p 的值一定是 NULL */
}
if(p == NULL)
{
/* 錯誤處理 */
}
/* -// 設定對端信息
memset(&recv_host, 0, sizeof(recv_host));
recv_host.sin_family = AF_INET;
recv_host.sin_addr.s_addr = inet_addr("127.0.0.1");
recv_host.sin_port = htons(6000);
*/
/* 發(fā)送信息 */
/* 在此處,發(fā)送端的IP地址和端口號等各類信息,隨著這個函數的調用,自動綁定在了套接字上 */
sendto(sock, mess, strlen(mess), 0, p->ai_addr, p->ai_addrlen);
/* 完成,關閉 */
freeaddrinfo(result); /* 實際上這個函數應該在使用完 result 的地方就予以調用 */
close(sock);
tcpdump 的 *nix 平臺,前者你可以選擇隨意監(jiān)聽一個機器,不多時就能看見 ARP
協(xié)議的使用,因為它使用的太頻繁了。ICMP 的應用中可以拿來查詢本機的子網掩碼之類的信息,大致通過向本子網內的所有主機發(fā)送該請求報文(包括自己,實際上就是廣播),后接收應答,得到信息unconnected 狀態(tài)下是會忽略 ICMP報文的。而 TCP 因為總是 connected 的,所以對于 ICMP報文能很好的捕捉。注意,對于
UDP而言,只有connected狀態(tài)下,才會收到 ICMP 報文,可以通過errno == ECONNREFUSED來確定,具體來說就是在你發(fā)送完本次數據之后,的下一次系統(tǒng)調用時會有這個想想,代表你的小心沒有被送到對方手里,而是被對方丟棄了。在沒有一個完備的思路以及良好的設計之前,
UDP是一個十分艱難的挑戰(zhàn),只有在TCP實在無法滿足我們的性能需求時,我們才來重新考慮UDP。