linux的套接字部分比較容易混亂,在這里稍微總結一下。
地址轉換函數在地址的文本表達式和它們存放在套接字地址結構中的二進制值進行轉換。
地址轉換函數有四個:其中inet_addr 和 inet_ntoa適用於IPv4,inet_pton 和 inet_ntop同時適於用IPv4和IPv6。
- 套接字地址結構分為IPv4套接字地址結構sockaddr_in和IPv6套接字地址結構sockaddr_in6。其中IPv4的套接字地址結構如下。
- IPv4套接字地址結構:(定義在<netinet/in.h>頭文件中)
1 struct in_addr{ 2 in_addr_t s_addr; // 3 }; 4 5 struct sockaddr_in{ 6 uint8_t sin_len; 7 sa_family_t sin_family; //套接字地址結構的地址族 8 in_port_t sin_port //TCP或UDP端口,一般為uint16_t 9 struct in_addr sin_addr; //IPv4地址,一般為uint32_t 10 char sin_zero[8]; 11 };
-
說明:POSIX規范只需要這個結構中的3個字段:sin_family、sin_addr和sin_port。對於符合POSIX的實現來說,定義額外的結構字段是可以接受的。幾乎所有的實現都增加了sin_zero字段,所以所有的套接字地址結構大小都至少是16字節。
- IPv4地址和TCP或UDP端口號在套接字地址結構中總是以網絡字節序來存儲,在使用這些字段時,必須要牢記這一點。
- 32位IPv4地址存在兩種不同的訪問方法。例如:如果serv第一位某個網際套接字地址結構,那么serv.sin_addr將按in_addr結構引用其中的32位IPv4地址,而serv.sin_addr.s_addr將按in_addr_t(通常是一個uint32_t)引用同一個32位Ipv4地址。在將它作為函數的參數時要注意使用正確的IPv4地址,因為編譯器對傳遞結構和傳遞正數的處理是完全不同的。
- sin_zero字段未曾使用,不過在填寫這種套接字地址結構時,總是把該字段置為0(總是在填寫前把整個結構置為0)
- 套接字地址結構僅在給定主機上使用:雖然結構中的某些字段(例如IP地址和端口號)用在不同主機之間的通信,但是結構本身並不在主機之間傳遞。
-
- IPv4套接字地址結構:(定義在<netinet/in.h>頭文件中)
- 通用套接字地址結構:
- 當作為一個參數傳遞進任何套接字函數時,套接字地址結構總是以引用形式(也就是以指向該結構的指針)來傳遞。然而以這樣的指針作為參數之一的任何套接字函數必須處理來自所有支持的任何協議族的套接字地址結構。在如何聲明所傳遞指針的數據類型上存在一個問題,有了ANSI C后解決辦法:void *。然而在ANSI C之前的解決辦法是在<sys/socket.h>中定義一個通用的套接字地址結構:
1 struct sockaddr{ 2 uint8_t sa_len; 3 sa_family_t sa_family; 4 char sa_data[14]; 5 };
於是套接字函數被定義為以指向某個通用套接字地址結構的一個指針作為其參數之一,例如bind函數的ANSI C函數原型:
int bind(int, struct sockaddr *, socklen_t); //一般是uint32_t
這就要求對這些函數的任何調用都必須要將指向特定於協議的套接字地址結構的指針進行強制類型轉換,變成指向某個通用套接字地址類型的指針,如:
struct sockaddr_in serv; bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
- 當作為一個參數傳遞進任何套接字函數時,套接字地址結構總是以引用形式(也就是以指向該結構的指針)來傳遞。然而以這樣的指針作為參數之一的任何套接字函數必須處理來自所有支持的任何協議族的套接字地址結構。在如何聲明所傳遞指針的數據類型上存在一個問題,有了ANSI C后解決辦法:void *。然而在ANSI C之前的解決辦法是在<sys/socket.h>中定義一個通用的套接字地址結構:
- 值--結果參數
- 當向一個套接字函數傳遞一個套接字地址結構時,該結構總是以引用形式來傳遞,也就是說傳遞的是一個指向該結構的一個指針。該結構的長度也作為一個參數來傳遞,但是其傳遞方式取決於該結構的傳遞方向:進程-->內核,內核-->進程
- 從進程到內核傳遞套接字地址結構的函數有3個:bind,connect,sendto。這些函數的一個參數是指向某個套接字地址結構的指針,另一個參數是該結構的整數大小,例如:
struct sockaddr_in serv; connect(sockdf, (SA *) &serv, sizof(serv));
- 從內核到進程傳遞套接字地址結構的函數有4個:accept, recvfrom,getsockname和getpeername。這4個函數的其中兩個參數是指向某個套接字地址結構的指針和指向表示該結構大小的整數的指針,如:
struct sockaddr_un cli; socklen_t len; len = sizeof(cli); getpeername(unixfd, (SA *) &cli, &len);
- 從進程到內核傳遞套接字地址結構的函數有3個:bind,connect,sendto。這些函數的一個參數是指向某個套接字地址結構的指針,另一個參數是該結構的整數大小,例如:
- 把套接字地址結構大小這個參數從一個整數改為指向某個整數變量的指針,其原因在於:當單數被調用時,結構大小是一個值,它告訴內核結構的大小,這樣內核在寫該結構時不至於越界;當函數返回時,結構大小又是一個結果,它告訴進程內核在該結構中究竟存儲了多少信息。這種類型的參數成為值-結果參數。
- 當向一個套接字函數傳遞一個套接字地址結構時,該結構總是以引用形式來傳遞,也就是說傳遞的是一個指向該結構的一個指針。該結構的長度也作為一個參數來傳遞,但是其傳遞方式取決於該結構的傳遞方向:進程-->內核,內核-->進程
- 網絡字節序 <--> 主機字節序:有四個函數用於網絡字節序和主機字節序之間的轉換:
- 以下兩個均返回網絡字節序
- uint16_t htons(uint16_t host16bitvalue);
- uint32_t htonl(uint32_t host32bitvalue);
- 以下兩個均返回主機字節序
- uint16_t ntohs(uint16_t net16bitvalue);
- uint32_t ntohs(uint32_t net32bitvalue);
- 地址轉換函數(兩組):
- inet_aton, inet_addr, inet_ntoa:這一組函數在點分十進制字符串("206.168.112.96")與它長度為32位的網絡字節序二進制值間轉換IPv4地址。原型如下:
#include <arpa/inet.h> int inet_aton(const char *strptr, struct in_addr *addrptr); //返回:若字符串有效則為1,否則為0 in_addr_t inet_addr(const char *strptr); //返回:若字符串有效則為32位二進制網絡字節序的IPv4地址,否則為INADDR_NONE char *inet_ntoa(struct in_addr inaddr); //返回:指向一個點分十進制數串的指針
//還有一個名為inet_network的函數和inet_addr類似,但是其返回的是主機字節序
in_addr_t inet_network(const char *strptr);inet_aton 和 inet_addr 兩個函數都是將一個點分十進制字符串轉換成一個32位的網絡字節序二進制值。inet_aton函數,如果addrptr指針為空,那么該函數仍然對輸入的字符串執行有效性檢查,但是不存儲任何結果。inet_addr函數存在一些問題:所有2^32個可能的二進制值都是有效的ip地址(0.0.0.0-255.255.255.255),但是當出錯時函數返回INADDR_NONE常量(通常是一個32位均為1的值),這意味着點分十進制數串255.255.255.255不能由該函數處理,因為它的二進制用來指示該函數失敗,inet_addr還存在一個潛在的問題:一些手冊聲明該函數出錯時返回-1而不是INADDR_NONE,這樣在對該函數的返回值和一個負常量進行比較時可能會發生問題。如今,inet_addr已經廢棄,新的代碼應該改用inet_ato函數,更好的解決方法是使用inet_pton函數。另外需要注意的是inet_ntoa函數的參數是in_addr的結構體而不是指針。
- inet_pton,inet_ntop這一組函數對IPv4和IPv6地址都適用。原型如下:
#include <arpa/inet.h> int inet_pton(int family, const char *strptr, void *addrptr); //返回:若成功則為1,若輸入不是有效的表達格式則為0,若出錯則為-1 const char *inet_ntop(int family, void *addrptr, char *strptr, size_t len); //返回:若成功則為指向結果的指針,若出錯則為NULL
//len:INET_ADDRSTRLEN INET6_ADDRSTRLEN
//inet_ntop函數的strptr不可以是一個空指針,調用者必須為目標存儲單元分配內存並制定其大小,調用成功時,這個指針就是該函數的返回值 - 地址轉換函數小結:
inet_pton(AF_INET),inet_aton, inet_addr 點分十進制數 -----------------------------------------------------> in_addr{} IPv4地址 <----------------------------------------------------- 32位二進制IPv4地址 inet_ntop(AF_INET),inet_ntoa
#include <stdio.h> #include <string.h> #include <arpa/inet.h> int main(int argc, char **argv) { const char *ip_str = "127.0.0.1"; char *ip_res; in_addr_t addr_t; struct in_addr addr; //1.str -> binary inet_aton(ip_str, &addr); printf("inet_aton::%x\n", addr); //1000007f addr_t = inet_addr(ip_str); printf("inet_addr::%x\n", addr_t); //1000007f inet_pton(AF_INET, ip_str, (void *)&addr); //1000007f printf("inet_pton::%x\n", addr); //2.binary -> str ip_res = inet_ntoa(addr); printf("inet_ntoa::%s\n", ip_res); //127.0.0.1 inet_ntop(AF_INET, &addr, ip_res, INET_ADDRSTRLEN); printf("inet_ntop::%s\n", ip_res); //127.0.0.1 return 0; } output: inet_aton::100007f inet_addr::100007f inet_pton::100007f inet_ntoa::127.0.0.1 inet_ntop::127.0.0.1
- inet_aton, inet_addr, inet_ntoa:這一組函數在點分十進制字符串("206.168.112.96")與它長度為32位的網絡字節序二進制值間轉換IPv4地址。原型如下: