### 1. 套接字結構
### 1) IPv4套接字地址結構
~~~
truct in_addr{
in_addr_t s_addr;
};
struct sockaddr_in{
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
~~~
我們通常只使用三個字段:sin_family,sin_addr和sin_port.如下圖所示:
~~~
bzero( &servaddr, sizeof( servaddr ) );
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(7777);
~~~
而一個實例如下:
~~~
#include <stdio.h>
#include <sys/stat.h>
#include <netinet/in.h>
int main( void )
{
printf("%d\n", htonl(INADDR_ANY));
printf("%d\n", htonl(16));
printf("%d\n", htons(13));
return 0;
}
~~~
程序輸出:
~~~
leichaojian@ThinkPad-T430i:~$ ./a.out
0
268435456
3328
~~~
### 2)通用套接字結構
?? 當作為一個參數傳遞進任何套接字函數時,套接字地址結構總是以引用形式來傳遞。然而以這樣的指針作為參數之一的任何套接字函數必須處理來自所支持的任何協議族的套接字地址結構。
~~~
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family; /*address family:AF_xxx value*/
char sa_data[14]; /*protocol-specific address*/
};
~~~
?? 于是套接字函數被定義為以指向某個通用套接字地址結構的一個指針作為其參數之一,類似bind函數:
~~~
bind(int, struct sockaddr *, socklen_t);
~~~
?? 我們調用bind的時候,必須進行強制類型轉換:
~~~
struct sockaddr_in serv;
.......
bind(sockfd, (struct sockaddr *)&serv, sizeof(serv));
~~~
### 3)新的通用地址結構
~~~
struct sockaddr_storage{
uint8_t sa_len;
sa_family_t sa_family;
};
~~~
?? sockaddr_storage類型提供的通用套接字地址結構相比sockaddr存在以下兩點差別:
(1)如果系統支持的任何套接字地址結構有對齊需求,那么sockaddr_storage能夠滿足最苛刻的對齊要求。
(2)sockaddr_storage足夠大,能夠容納系統支持的任何套接字地址結構。
備注:sockaddr_storage的其他字段對用戶來說是透明的。
### 2) 值-結果參數
?? 當往一個套接字函數傳遞一個套接字地址結構時,該結構總是以引用形式來傳遞,也就是說傳遞的是指向該結構的一個指針。該結構的長度也作為一個參數來傳遞,不過其傳遞方式取決于該結構的傳遞方向:是從進程到內核,還是從內核到進程。
?? (1)從進程到內核傳遞套接字地址結構的函數有3個:bind,connect和sendto。這些函數的一個參數是指向某個套接字地址結構的指針,另一個參數是該結構的整數大小,例如:
~~~
struct sockaddr_in serv;
connect( sockfd, ( SA * )&serv, sizeof( serv ) );
~~~
?? 既然指針和指針所指內容的大小都傳遞給了內核,于是內核知道到底需從進程復制多少數據進來。
?? (2)從內核到進程傳遞套接字地址結構的函數有4個:accept,recvfrom,getsockname和getpeername。
~~~
struct sockaddr_un cli;
socklen_t len;
len = sizeof( cli );
getpeername( unixfd, ( SA * )&cli, &len );
/*len may have changed*/
~~~
?? 這里len既作為值傳遞進去,又作為結果返回回來。
### 3) 字節排序函數
### (1)大端模式---小端模式
~~~
#include <stdio.h>
int main( int argc, char **argv )
{
union{
short s;
char c[ sizeof( short ) ];
} un;
un.s = 0x0102;
if ( sizeof( short ) == 2 ){
if ( un.c[ 0 ] == 1 && un.c[ 1 ] == 2 )
printf("big-endian\n");
if ( un.c[ 0 ] == 2 && un.c[ 1 ] == 1 )
printf("little-endian\n");
else
printf("unknown\n");
}
else
printf("sizeof(short)=%d\n", sizeof( short ) );
exit( 0 );
}
~~~
?? 程序輸出:
~~~
leichaojian@ThinkPad-T430i:~$ ./a.out
little-endian
~~~
### (2) 主機字節序和網絡字節序之間相互轉換的四個函數
~~~
#include <netinet/in.h>
uint16_t htons( uint16_t host16bitvalue );
uint32_t htonl( uint32_t host32bitvalue );
------------均返回:網絡字節序的值
uint16_t ntohs( uint16_t net16bitvalue );
uint32_t ntohl( uint32_t net32bitvalue );
------------均返回:主機字節序的值
~~~
?? 在這些名字中,h代表host,n代表network,s代表short,l代表long。如今我們應該把s視為16位的值(如TCP或UDP端口號),把l視為一個32位的值(例如IPv4地址)
測試用例如下:
~~~
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char **argv)
{
uint32_t ipAddr;
uint16_t portAddr;
struct sockaddr_in servaddr;
char buff[1024];
bzero(&servaddr, sizeof(servaddr));
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
ipAddr = ntohl(servaddr.sin_addr.s_addr);
printf("ipAddr is:%d\n", ipAddr);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_addr.s_addr = htonl(ipAddr);
inet_ntop(AF_INET, &servaddr.sin_addr, buff, sizeof(buff));
printf("the ip addr is:%s\n", buff);
printf("------------\n");
portAddr = htons(9877);
printf("the port 9877 netword port is:%d\n", portAddr);
printf("the port is:%d\n", ntohs(portAddr));
return 0;
}
~~~
程序輸出:
~~~
leichaojian@ThinkPad-T430i:~$ ./test 127.0.0.1
ipAddr is:2130706433
the ip addr is:127.0.0.1
------------
the port 9877 netword port is:38182
the port is:9877
~~~
### 4) 相關的重要函數
### (1)inet_aton和inet_ntoa函數
~~~
#include <arpa/inet.h>
int inet_aton( const char *strptr, struct in_addr *addrptr );
返回:若字符串有效則為1,否則為0
char *inet_ntoa( struct in_addr inaddr );
返回:指向一個點分十進制數串的指針
~~~
測試程序如下:
~~~
1 #include <stdio.h>
2 #include <arpa/inet.h>
3
4 int main( int argc, char **argv )
5 {
6 struct in_addr addr;
7 char *pAddr;
8 inet_aton( argv[ 1 ], &addr );
9 printf( "%d\n", addr );
10 pAddr = inet_ntoa( addr );
11 printf("%s\n", pAddr );
12
13 return 0;
14 }
~~~
程序輸出:
~~~
leichaojian@ThinkPad-T430i:~$ ./a.out 127.0.0.1
16777343
127.0.0.1
~~~
### (2)inet_pton和inet_ntop函數
~~~
#include<arpa/inet.h>
int inet_pton( int family, const char *strptr, void *addrptr );
const char *inet_ntop( int family, const void *addrptr, char *strptr, size_t len );
~~~
?? 這里p和n分別代表“表達”(presentation)和“數值”(numeric).即將點分十進制IP地址轉換為套接字結構中的二進制值,或者逆向轉換:
~~~
if ( inet_pton( AF_INET, argv[ 1 ], &servaddr.sin_addr ) <= 0 ){
printf("inet_pton error for %s", argv[1]);
exit(1);
}
~~~
### (3)readn,writen(作者自己編寫的)
?? 字節流套接字上調用read或write輸入或輸出的字節數可能比請求的數量少,然而這不是出錯的狀態。這個現象的原因在于內核中用于套接字的緩沖區可能已達到極限,此時所需的是調用者再次調用read或write函數,以輸入或輸出剩余的字節。
~~~
ssize_t readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 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);
}
ssize_t writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0){
if ((nwritten = write(fd, ptr, nleft)) <= 0){
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return (-1);
}
nleft -= nwritten;
ptr += nwritten;
}
return (n);
}
~~~