1.套接字地址結構
1).IPv4套接字地址結構
IPv4套接字地址結構通常也稱為“網際套接字地址結構”,它以sockaddr_in命名,定義在<netinet.h>頭文件中,如下所示
struct in_addr{ in_addr_t s_addr; //32位的IPv4網絡字節序 } struct sockaddr_in{ uint8_t sin_len; //帶符號8位整數地址結構長度 sa_family_t sin_family; //協議族,IPv4為AF_INET in_port_t sin_port; //端口號 struct in_addr sin_addr; //32位IPv4網絡字節序地址 char sin_zero[8]; //填充對齊位,未使用 };
2).通用套接字地址結構
存在通用套接字地址結構的原因是:在調用任何需要套接字地址結構做為參數的函數時,套接字地址結構總是以引用的方式(指針)傳遞的。不同的協議有不同的套接字地址結構,函數的參數怎么聲明這些套接字地址結構的指針類型是一個問題,於是就定義了一個通用套接字地址結構,所有需要套接字地址結構做參數的函數的這個形參都被聲明為指向這個通用套接字地址結構的指針的類型。其他套接字地址結構的指針被強制轉換為通用套接字地址結構的指針類型,ANSI C 定義了 void * 來解決這個問題
struct sockaddr { uint8_t sa_len; sa_family_t sa_family; /* address family: AF_xxx value */ char sa_data[14]; /* protocol-specific address */ };
強制轉換實例:
struct sockaddr_in serv; /* IPv4 socket address structure */ /* fill in serv{} */ bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
從應用程序開發的角度來看,這些通用套接字結構的唯一用途就是對指向特定於協議的套接字地址結構的指針執行類型強制轉換
2).IPv6套接字地址結構
IPv6套接字地址結構在<netinet/in.h>中定義,如下所示
struct in6_addr{ uint8_t s6_addr[16]; //128位IPv6網絡字節序地址 }; #define SIN6_LEN struct sockaddr_in6{ uint8_t sin6_len; //這個結構的長度 sa_family_t sin6_family; //協議族 AF_INET6 in_port_t sin6_port; //端口號,網絡字節序 uint32_t sin6_flowinfo; //流信息,未定義 struct in6_addr sin6_addr; //IPv6地址 uint32_t sin6_scope_id; };
說明:
- 如果當前系統支持套接字地址結構中的長度字段,則SIN6_LEN常值必須定義
- IPv6的地址族是AF_INET6 而IPv4 是AF_INET.
- 結構體字段的先后順序做過編排的,使得結果sockaddr_in6結構本身64位對齊,那么128位的sin6_addr字段特是64位對齊的
(其他說明見UNP3)
3).新的通用套接字地址結構
不像struct sockaddr,新的struct sockaddr_storage足以容納系統所支持的任何套接字地址結構,sockaddr_storage結構在<netinet/in.h>頭文件中定義
struct sockaddr_storage { uint8_t ss_len; /* length of this struct (implementation dependent) */ sa_family_t ss_family; /* address family: AF_xxx value */
/*其他字段對用戶來說的透明的 故沒有列出*/ };
sockaddr_storage和sockaddr的主要差別
- sockaddr_storage通用套接字地址結構滿足對齊要求
- sockaddr_storage通用套接字地址結構足夠大,能夠容納系統支持的任何套接字地址結構。
4).套接字地址結構的比較
下圖給出了給套接字地址結構的直觀比較
2.值-結果參數
一個套接字函數傳遞一個套接字地址結構時候,該結構總以引用形勢來傳遞,也就是說傳遞的指向該結構的一個指針,該結構的床度也作為一個參數來傳遞,不過其傳遞方式取決於該結構的傳遞方向:是從進程到內核,還是從內核到進程。
1).從進程到內核傳遞套接字結構函數:bind、connect和sendto,這些函數的一個參數是指向某個套接字地址結構的指針,另一個參數是該結構體的整數大小
struct sockaddr_in serv; /* fill in serv{} */ connect (sockfd, (SA *) &serv, sizeof(serv));
2).從內核到進程傳遞套接字地址結構的函數:accept、recvfrom、getsockname和getpeername。這四個參數的其中兩個參數指向某個套接字結構體的指針和指向表示該結構體大小的整數變量的指針,eg:
struct sockaddr_un cli; /* Unix domain */ socklen_t len; len = sizeof(cli); /* len is a value */ getpeername(unixfd, (SA *) &cli, &len); /* len may have changed */
為何將結構大小由整數改為指向整數的指針呢?這是因為:當函數被調用時,結構大小是一個值(value), 此值告訴內核該結構的大小,使內核在寫此結構時不至於越界),當函數返回時,結構大小又是一個結果(result,它告訴進程內核在此結構中確切存儲了多少信息),這種參數叫做值-結果參數(value-result)。
3.字節排序函數
- 大端字節序:高字節在低地址
- 小端字節序:低字節在高地址
- 網絡字節序:網絡字節序采用大端字節序
- 主機字節序:本地主機使用的字節序,可能為大端或小端
如下圖
因為套接字地址結構中的某些字段必須按照網絡字節序進行維護,所以必須關注主機字節序和網絡字節序之間的相互轉換,這兩種字節序之間的轉換使用下面的4個函數:
#include <netinet/in.h> uint16_t htons(uint16_t host16bitvalue); //主機to網絡 short uint32_t htonl(uint32_t host32bitvalue); //主機to網絡 long //均返回:網絡字節序的值 uint16_t ntohs(uint16_t net16bitvalue); //網絡to主機 short //均返回:主機字節序的值 uint32_t ntohl(uint32_t net32bitvalue); //網絡to主機 long
顯然在這些函數名字中,h表示network,s代表short,l代表long。
4.字節操縱函數
1).
#include <strings.h> void bzero(void *dest,size_t nbytes); //將目標字符串制定數目的字節置0 void bcopy(const void *src,void *dest,size_t nbytes); //拷貝指定字節 int bcmp(const void*ptrl,const void *ptr2,size_t nbytes); //若相等返回0
2).
#include<string.h> void *memset(void *dest,const void *c,size_t len); void *memcpy(void *dest,const void *src,size_t nbytes); int memcpy(const void *ptrl,const void *ptr2,size_t nbytes); //返回:若相等則為0,否則為<0或者>0
memset把目標字節串指定數目的字節置為值c。memcpy類似bcopy,不過兩個指針參數的順序相反,當源字節串與目標字節串重疊時,bcopy能夠正確處理,但是memcpy的操作結果卻不可知,memcmp比較兩個任意的字符串
5.inet_aton、inet_addr和inet_ntoa函數
inet_aton、inet_addr和inet_ntoa在點分十進制數串(eg:“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位二進制的網絡字節序地址;若有錯,返回INADDR_NONE char *inet ntoa(struct in_addr inaddr); //返回:指向點分十進制數串的指針
第一個函數inet_aton將strptr所指的C字符串轉換成32位的網絡字節序二進制值,並通過指針addrptr來存儲。如果成功返回1,否則返回0,inte_addr進行相同的轉換,返回值為32位的網絡字節序二進制值,一般用inet_aton來代替代替inet_addr.
函數inet_ntoa將一個32位的網絡字節序二進制IPv4地址轉換成相應的點分十進制數串。由函數返回值所指的串駐留在靜態內存中,這意味着函數是不可重入的。注意這個函數以結構為參數,而不是指向結構的指針。
6.inet_pton和inet_ntop函數
函數名中p和n分別代表表達(presentation)和數值(numeric)地址的表達式通常是ASCII字符串,數值格式則是存放在套接字地址結構中的二進制值
#include <arpa/inet.h> int inet_pton(int family, const char *strptr, void *addrptr); //返回:若成功為1,若輸入不是有效的表達式為0,若出錯為-1 const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); //返回:若成功則為指向結果的指針,若出錯則為null
inet_pton函數嘗試轉換由strptr指針指向的字符串,並通過addrptr指針存放二進制結果,inet_ntop函數進行相反的轉換,從數值格式(addrptr)轉換到表達格式(strptr),len參數是目標存儲單元的大小
下圖給出了地址轉換函數小結
7.readn、writen和readline函數
下面的函數實際上就是read、writen函數增強版
#include "unp.h" ssize_t readn(int filedes, void *buff, size_t nbytes); ssize_t writen(int filedes, const void *buff, size_t nbytes); ssize_t readline(int filedes, void *buff, size_t maxlen); //均返回:讀或寫的字節數,若出錯則為-1
實現代碼如下:
//readn #include "unp.h" ssize_t readn(int fd,void *vptr,size_t n) { size_t nleft; ssize_t nread; char *ptr; ptr = vptr; nleft = n; while(nnleft > 0) { if((nread = read(fd,ptr,nleft)) < 0 ) { if(errno == EINTR) { nread = 0; } else { return -1; } } else if(nread == 0) { break; } nleft -= nread; ptr += nread; } return (n-nleft); }
//written #include "unp.h" ssize_t written(int fd,void *vptr,size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while(nlfet > 0) { if((nwritten = writen(fd,ptr,nleft)) <= 0) { if(nwritten < 0 && errno ==EINTR) { nwritten =0; } else { return -1; } } nleft -= nwritten; ptr += nwritten; } return n; }
//readline #include "unp.h"
ssize_t readline(int fd, void *vptr, size_t maxlen) { ssize_t n,rc; char c,*ptr; ptr = vptr; for(n = 1; n < maxlen; n++)
{ if( (rc = read(fd, &c)) == 1)
{ *ptr ++ = c; if(c == '\n') break; }
else if (r c== 0)
{ *ptr = 0; return (n-1); }else return (-1); } *ptr = 0; return (n); }