一、三种地址结构体
在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);
}