Socket 編程-地址轉換

一、三種地址結構體

在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_instruct 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 *) &in;
  __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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章