標籤:
IPv4通訊端地址結構:
通常也被成為“網際通訊端地址結構”,以sockaddr_in命名,定義在<netinet/in.h>標頭檔中。
struct in_addr { in_addr_t s_addr; // 32bits的ip地址,如0xFF000001 -> 127.0.0.1 };
/* Structure describing an Internet socket address. */struct sockaddr_in { uint8_t sin_len; /* length of structure (16) */ sa_family_t sin_family; /* AF_INET */ in_port_t sin_port;/* Port number. 16bits */ struct in_addr sin_addr;/* Internet address. */ char sin_zero[8]; /* unused */ }; // sin_family,sin_port,sin_addr是一定支援的3個成員
IPv4地址和TCP或UDP連接埠號碼在通訊端地址結構中總是以網路位元組序(區別於主機位元組序)來儲存。
之所以網際地址(in_addr)是一個結構體,是因為以前這個結構體中允許訪問2個16位的值,用於劃分A、B、C類,而現在子網劃分之後,這些union結構不再需要。
sin_zero欄位未曾使用,但我們總是把該欄位置為0,按照慣例,我們總是在填寫前把整個結構置為0。
-------------------------- 分割線 ---------------------------------
以上是IPv4通訊端地址結構,然而通訊端函數是通用的,並且總是接收一個通訊端地址結構的指標(eg, sockaddr_in serv; bind(sockfd, (sockaddr *) &serv, sizeof(serv)); ),可以看到第二個參數被轉成了sockaddr類型,這是通用通訊端地址結構。在通訊端函數定義的時候,還沒有通用的指標類型void *,所以必須傳入一個恰當的類型,否則會報錯,於是在<sys/socket.h>定義了一個通用的通訊端地址結構。
通用通訊端地址結構用途就是對指向特定於協議的通訊端地址結構的指標執行類型強制轉換。
struct sockaddr{ uint8_t sa_len; sa_family_t sa_family; /* address family: AF_xxx value */ char sa_data[14]; /* protocol-specific address */};
-------------------------- 分割線 ---------------------------------
IPv6地址為128位長,但通常寫作8組,每組為四個十六進位數的形式,如 FE80:0000:0000:0000:AAAA:0000:00C2:0002。
IPv6通訊端地址結構在<netinet/in.h>標頭檔中定義:
struct in6_addr {uint8_ts6_addr[16]; };#define SIN6_LENstruct sockaddr_in6 { uint8_t sin6_len; /* length of this struct (28) */ sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port;/* Transport layer port # */ uint32_t sin6_flowinfo;/* IPv6 flow information */ struct in6_addr sin6_addr;/* IPv6 address */ uint32_t sin6_scope_id;/* IPv6 scope-id */ };
如果系統支援通訊端地址結構中的長度欄位,那麼SIN6_LEN常值必須定義。
IPV6的地址族是AF_INET6,而IPv4的地址族是AF_INET。
結構中欄位的先後順序做過編排,使得如果sockaddr_in6的結構本身是64位對齊的,那麼128位的sin6_addr欄位也是64位對齊的。在一些64位處理機上,如果64位元據儲存在某個64位邊界位置,那麼對它的訪問將得到最佳化處理。
sin6_flowinfo欄位分為兩個欄位:低序20位是流標(flow label),高序12位保留。
對於具備範圍的地址(scoped address),sin6_scope_id欄位標識其範圍(scope),最常見的是鏈路局部地址(link-local address)的介面索引(interface index)
-------------------------- 分割線 ---------------------------------
新的通用通訊端地址結構:新的結構克服了sockaddr的一些缺點,新的sockaddr_storage足以容納系統所支援的任何通訊端地址結構。
struct sockaddr_storage { uint8_t ss_len; /* length of this struct (implementation dependent) */ sa_family_t ss_family; /* address family: AF_xxx value */ /* implementation-dependent elements to provide: * a) alignment sufficient to fulfill the alignment requirements of * all socket address types that the system supports. * b) enough storage to hold any type of socket address that the * system supports. */};
sockaddr_storage能夠滿足最苛刻的對齊要求。
sockaddr_storage足夠大,可以容納系統支援的任何通訊端地址結構,除了ss_family和ss_len(如果有),其他的欄位可以任意放置(對使用者透明),sockaddr_storage結構必須強制轉換成或複製到適合於ss_family欄位所給出地址類型的通訊端地址結構中,才能訪問其他欄位。
-------------------------- 分割線 ---------------------------------
值-結果參數(說的是傳遞的參數作為返回結果的引用,eg, func(&res) ):
當往一個通訊端函數傳遞一個通訊端地址結構時,該結構總是以引用形式來傳遞,也就是說傳遞的是指向該結構的一個指標。該結構的長度也作為一個參數來傳遞,不過其傳遞方式取決於該結構的傳遞方向:是從進程的核心,還是從核心到進程。
1)從進程到核心傳遞通訊端地址結構的函數有3個:bind、connect、sendto。這些函數的一個參數是指向某個通訊端地址結構的指標,另一個參數是該結構的整數大小。(核心需要知道到底從進程複製了多少資料進來)
2)從核心到進程傳遞通訊端地址結構的函數有4個:accept、recvfrom、getsockname和getpeername。這4個函數的其中兩個參數是指向某個通訊端地址結構的指標和指向表示該結構大小的整數變數的指標(這是一個結果,所以是引用傳值)。
值-結果參數返回的結果:如果通訊端地址結構是固定長度(如IPv4 (16) 和IPv6 (28) ),則傳回值總是固定長度;對於可變長度(unix域等),傳回值可能小於該結構的最大長度。
-------------------------- 分割線 ---------------------------------
位元組排序函數
小端位元組序(little-endian):低序位元組儲存在起始地址,如0x12345678,在記憶體中從小到大的地址,儲存序列是 78 56 34 12
大端位元組序(big-endian):高序位元組儲存在起始地址,如0x12345678,在記憶體中從小到大的地址,儲存序列是 12 34 56 78
以上兩種格式都有系統使用!我們把某個給定系統所用的位元組序成為主機位元組序(host byte order)
#include "../unpv13e/unp.h"#include "../unpv13e/apueerror.h"// 以上路徑是我自己的配置// page.64 確定主機位元組序的程式(小端對齊還是大端對齊)intmain(int argc, char **argv){ union { short s; char c[sizeof(short)]; } un; un.s = 0x0102; printf("%s: ", CPU_VENDOR_OS); // 輸出CPU類型、廠家和作業系統版本。 if (sizeof(short) == 2) { if (un.c[0] == 1 && un.c[1] == 2) printf("big-endian\n"); else if (un.c[0] == 2 && un.c[1] == 1) printf("little-endian\n"); else printf("unknown\n"); } else printf("sizeof(short) = %d\n", (int)sizeof(short)); exit(0);}
問題1:網路位元組序和主機位元組序的區別?
答:網際協議使用大端位元組序來傳送這些多位元組整數,而系統使用的主機位元組序可能是大端也可能是小端。
問題2:具體實現方法是怎樣?
答:通訊端地址結構的欄位按照網路位元組序(大端)進行維護,所以要通過函數進行轉換。(以下h:host,n:network,s:short->16bits port,l:long->32bits ipv4)
htons 返回網路位元組序的連接埠
htonl 返回網路位元組序的ip
ntohs 返回主機位元組序的連接埠
ntohl 返回主機位元組序的ip
注意:事實上在64位系統中,長整數雖然佔用64位,to long的函數操作的仍然是32位的值。
在大端位元組序的系統中,這4個函數被定義為空白宏。
位元組操縱函數
處理字串的函數被放在string.h中,然而像通訊端地址結構這種多位元組欄位,需要全部清0,則需要用到位元組操縱函數(有2組):
#include <strings.h>// strings.h是從BSD系UNIX系統繼承而來,裡面定義了一些字串函數,參考自 http://blog.csdn.net/xin_yu_xin/article/details/38672137void bzero(void *dest, size_t nbytes);void bcopy(const void *src, void *dest, size_t nbytes);int bcmp(const void *ptr1, const void *ptr2, size_t nbytes); // 0為相等,非0為不相等
#include <string.h>// ANSI C標準void *memset(void *dest, int c, size_t len);void *memcpy(void *dest, const void *src, size_t nbytes);int memcmp(const void *ptr1, const void *ptr2, size_t nbytes); // 0為相同,非0為不相同
做了個測試,使用memset的時候,c的值置為1,對於一個int型的數,得到一個比較大的結果,16843009,於是查了一下發現一篇文章。證明了memset是根據位元組來填充的,所以實際上memset之後,值為0x01010101,即16843009(10),stackoverflow中也有一個同樣的問題解答。
-------------------------- 分割線 ---------------------------------
地址轉換函式
作用:從點分十進位數串(如:206.168.112.96)轉成網路位元組序二進位值
兩組函數:
(1) inet_aton , inet_addr , inet_ntoa (僅適用於IPv4)
#include <arpa/inet.h>int inet_aton(const char *strptr, struct in_addr *addrptr); // 若字串有效則返回1,否則為0,。如果addrptr指標為空白,那麼該函數仍然對輸入的字串執行有效性檢查,但是不儲存任何結果in_addr_t inet_addr(const char *strptr); // 字串有效則返回32位二進位網路位元組序的IPv4地址,否則返回INADDR_NONE(通常是255.255.255.255,這意味著這個有限廣播位址不能由該函數來處理,還有一個問題是一些編譯器編譯的程式將返回-1的結果,而不是INADDR_NONE,所以這個函數現在已經被廢棄,要用inet_aton或inet_pton來代替)char *inet_ntoa(struct in_addr inaddr); // 注意,參數是一個結構而不是一個結構指標(這是非常罕見的..),傳回值是指向一個點分十進位數串的指標,該函數的傳回值指向的字串是駐留在靜態記憶體中的,以為著該函數是不可重新進入的(後面的概念)
可重新進入函數:可重新進入函數主要用於多任務環境中,一個可重新進入的函數簡單來說就是可以被中斷的函數,重入可以理解為重複進入,也就是說,可以在這個函數執行的任何時刻中斷它,轉入OS調度下去執行另外一段代碼,而返回控制時不會出現什麼錯誤;而不可重新進入的函數由於使用了一些系統資源,比如全域變數區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函數是不能運行在多任務環境下的。
(2) inet_pton , inet_ntop (對IPv4和IPv6都適用) p for presentation(表達) n for numeric(數值)
#include <arpa/inet.h>int inet_pton(int family, const char *strptr, void *addrptr);// 成功則返回1,輸入不是有效表達格式則返回0,出錯返回-1const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);// 成功則返回指向結果的指標,出錯返回NULL,len是目標儲存單元的大小,用於防止緩衝區溢位,為了有助於指定這個大小,在<netinet/in.h>標頭檔中定義了// #define INET_ADDRSTRLEN 16// #define INET6_ADDRSTRLEN 46// 如果len太小,不足以容納表達格式的結果(包括結尾的Null 字元),則返回一個null 指標,置errno為ENOSPC,strptr參數不可以是一個null 指標,必須先分配大小,調用成功時,這個指標就是該函數的傳回值。
只支援IPv4的inet_pton和inet_ntop函數的簡單定義:
intinet_pton(int family, const char *strptr, void *addrptr){ if (family == AF_INET) { struct in_addr in_val; if (inet_aton(strptr, &in_val)) { memcpy(addrptr, &in_val, sizeof(struct in_addr)); return (1); }return(0); }errno = EAFNOSUPPORT; return (-1);}
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len){const u_char *p = (const u_char *) addrptr;if (family == AF_INET) {chartemp[INET_ADDRSTRLEN];snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);if (strlen(temp) >= len) {errno = ENOSPC;return (NULL);}strcpy(strptr, temp);return (strptr);}errno = EAFNOSUPPORT;return (NULL);}
還有一些unp自行定義的函數,chap 3.8 (page 70~72),這裡不再說明。
read和write函數 (待寫 page 72)
【UNIX網路編程】通訊端編程簡介