一、三種地址結構體
在Socket編程中,有三種常見的結構類型,它們用來存放socket地址信息。這三種結構類型分別爲struct in_addr、struct sockaddr、struct sockaddr_in
1. struct in_addr
struct in_addr
用來存儲IP地址,對於IPv4來說,IP地址爲32位無符號整數。其定義在頭文件 <netinet/in.h>
中,詳細信息如下:
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
2. struct sockaddr
struct sockaddr
結構用來保存套接字地址信息,其定義在頭文件 <bits/socket.h>
中,詳細信息如下:
struct sockaddr
{
unsigned short int sa_family; /* 地址家族, AF_xxx */
char sa_data[14]; /*14字節協議地址*/
};
sa_family
是地址族類型,常見爲AF_INET
。sa_data
包含套接字中的IP地址信息和端口信息。
3. struct sockaddr_in
struct sockaddr
結構中 sa_data
字段包含較多信息,不利於方便編程和對其進行賦值,因此建立了struct sockaddr_in
結構,該結構與 struct sockaddr
結構大小相等,能更好處理struct sockaddr
結構中的數據。
其定義在頭文件 <netinet/in.h>
中,詳細信息如下:
struct sockaddr_in
{
unsigned short int sin_family;
uint16_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[8]; /* sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr) = 16 - 2 - 2 - 4 = 8*/
};
sin_zero
應該使用函數bzero()
或 memset()
來全部置零。它被加入到這個結構,以使struct sockaddr_in
的長度和 struct sockaddr
一樣
通過sin_zero
補 0 ,sockaddr_in
結構體 和 sockaddr
結構體等效,一個指向 sockaddr_in
結構體的指針也可以被指向結構體 sockaddr
並且代替它。
4. 總結
- 結構體
struct in_addr
用於存儲IP地址; - 結構體
struct sockaddr
用於存儲地址信息(包含地址族、端口和IP地址(struct in_addr
)),但是IP信息和端口信息都在同一個變量(sa_data
)中表示; - 結構體
struct sockaddr_in
與struct sockaddr
等效,只是將每個信息都用一個變量表示,使用時更清晰方便。
二、IP地址轉換
IP地址轉換函數是指完成點分十進制IP地址與二進制IP地址之間的相互轉換。
IP地址轉換主要有inet_aton、inet_addr和inet_ntoa這三個函數完成。
1. 將點分十進制IP地址轉換爲二進制地址
- in_addr_t inet_addr (const char *cp)
- int inet_aton (const char *cp, struct in_addr *inp)
1. “ntoa"的含義是"network to ascii”,“aton"的含義是"ascii to network”
2. inet_addr 返回二進制地址,返回值是 in_addr_t 類型(struct in_addr結構體中的變量)
3. inet_aton 將生成的二進制地址寫入第二個參數,參數類型爲 指向struct in_addr的指針
2. 將二進制地址轉換爲點分十進制IP地址
- char *inet_ntoa(struct in_addr in)
1. inet_ntoa() 將結構體 in_addr 作爲一個參數。
2. 同樣需要注意的是它返回的是一個指向靜態內存的指針。
3. 代碼示例:
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
char *a1, *a2;
struct sockaddr_in ina;
struct sockaddr_in ina2;
ina.sin_addr.s_addr = inet_addr("198.92.129.1");
inet_aton("132.241.5.10", &ina2.sin_addr);
a1 = inet_ntoa(ina.sin_addr); /* 這是198.92.129.1 */
printf("address 1: %s\n", a1);
a2 = inet_ntoa(ina2.sin_addr); /* 這是132.241.5.10 */
printf("address 1: %s\n", a1);
printf("address 2: %s\n", a2);
return 0;
}
注意,以上代碼,輸出的是:
address 1: 198.92.129.1
address 1: 132.241.5.10
address 2: 132.241.5.10
而不是
address 1: 198.92.129.1
address 1: 198.92.129.1
address 2: 132.241.5.10
前面說到,inet_ntoa() 返回的是一個指向靜態內存的指針
所以每次調用 inet_ntoa(),它就將修改此指針指向的內存的內容,然後返回此指針,因此會覆蓋上次調用時所得的IP地址
a1指針 與 inet_ntoa()返回的指針 指向同一塊內存。
當第二次調用inet_ntoa()時,inet_ntoa() 控制的指針指向的內容發生變化,即a1指針指向的內容發生變化,因此,最終a1,a2指向同一個位置,表示相同的信息。
因此如果需要保存返回的IP地址,需要使用strcpy或其它拷貝函數
靜態內存的邏輯大致如下:
#include <stdio.h>
#include <stdlib.h>
static char local_buf[8];
static char* buffer = local_buf;
char* test(char* str)
{
sprintf(buffer, "%s", str);
return buffer;
}
int main()
{
char* p1 = "p1";
char* p1_test = test(p1);
printf("p1_test:%s\n", p1_test);
char* p2 = "p2";
char* p2_test = test(p2);
printf("p1_test:%s\n", p1_test);
printf("p2_test:%s\n", p2_test);
return 0;
}
輸出爲
p1_test:p1
p1_test:p2
p2_test:p2
而不是
p1_test:p1
p1_test:p1
p2_test:p2
附錄. inet_ntoa 源碼
位於 glibc-2.4/inet/Inet_ntoa.c
文件:
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <bits/libc-lock.h>
/* The interface of this function is completely stupid, it requires a
static buffer. We relax this a bit in that we allow at least one
buffer for each thread. */
/* This is the key for the thread specific memory. */
static __libc_key_t key;
/* If nonzero the key allocation failed and we should better use a
static buffer than fail. */
static char local_buf[18];
static char *static_buf;
/* Destructor for the thread-specific data. */
static void init (void);
static void free_key_mem (void *mem);
char *
inet_ntoa (struct in_addr in)
{
__libc_once_define (static, once);
char *buffer;
unsigned char *bytes;
/* If we have not yet initialized the buffer do it now. */
__libc_once (once, init);
if (static_buf != NULL)
buffer = static_buf;
else
{
/* We don't use the static buffer and so we have a key. Use it
to get the thread-specific buffer. */
buffer = __libc_getspecific (key);
if (buffer == NULL)
{
/* No buffer allocated so far. */
buffer = malloc (18);
if (buffer == NULL)
/* No more memory available. We use the static buffer. */
buffer = local_buf;
else
__libc_setspecific (key, buffer);
}
}
bytes = (unsigned char *) ∈
__snprintf (buffer, 18, "%d.%d.%d.%d",
bytes[0], bytes[1], bytes[2], bytes[3]);
return buffer;
}
/* Initialize buffer. */
static void
init (void)
{
if (__libc_key_create (&key, free_key_mem))
/* Creating the key failed. This means something really went
wrong. In any case use a static buffer which is better than
nothing. */
static_buf = local_buf;
}
/* Free the thread specific data, this is done if a thread terminates. */
static void
free_key_mem (void *mem)
{
free (mem);
__libc_setspecific (key, NULL);
}