Contents

linux socket programming(二): socket 中用來存放地址的 sockaddr

Contents

sockaddr

sockaddrsocket 的通用地址結構,就如同一開始提到的,socket 除了在網路領域之外,也可以在很多不同的地方用來通訊。

sockaddr 結構,定義如下

typedef unsigned short int sa_family_t;

#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

struct sockaddr {
    __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
    char sa_data[14];		/* Address data.  */
};

// 上面的結構把巨集展開後,等價於下方的資料結構
struct sockaddr {
    unsigned short int sa_family; // 2 bytes
    char sa_data[14];             // 14 bytes
};

後來的更新中,為了讓龐大的程式碼可讀性上升,新增了 sockaddr_in 的結構用來存取網路相關的應用, in 指的是 internetsockaddr_in 專門用來存 IPv4 的相關地址。

IPv6 則是使用 sockaddr_in6 結構,在本文章主要會著重在 IPv4 相關的範例。

typedef uint32_t in_addr_t; // 4 byte
struct in_addr {
    in_addr_t s_addr;
};

struct sockaddr_in {    
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			    /* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
};

struct sockaddr_in {
    // sa_family_t sin_family
    unsigned short int sin_family; // 2 bytes
    unsigned short int sin_port;   // 2 bytes
    struct in_addr sin_addr;       // 4 bytes
    unsigned char sin_zero[8];     // 填充,讓 sockaddr_in 的 size 跟 sockaddr 相同
};

這邊觀看原始碼會覺得奇怪,為什麼還需要使用 sin_zero 來做填充的動作。

原因是很多 socketapi,參數都需要填入 sockaddrsockaddr_in 則是後來加入的 struct。 今天如果我們 address 的資料是用 sockaddr_in 來儲存,並且想調用相關的函式時,我們就需要強制轉型。

假設今天用 socket 的場景不是網路,也會有對應的結構來存地址,在呼叫 socket 通用的 api 時,就可以使用強制轉型的方式,讓不同的結構呼叫同一個函式。

實際範例: unix

在後面的例子中也會實際調用,下方的程式碼可以先作為參考。

#define serverIP
#define serverPort 12000

// 建立一個 sockaddr_in 結構,存著 server 的相關資料
struct sockaddr_in serverAddr = {
    .sin_family = PF_INET,
    .sin_addr.s_addr = inet_addr(serverIP),
    .sin_port = htons(serverPort)
};

bind(socket_fd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr));