sockaddr
sockaddr 是 socket 的通用地址結構。如同上一篇提到的,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 代表 internet,sockaddr_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 做填充?
原因是 sockaddr_in 是後來才加入的結構,而大多數 socket API 的參數型別都是 sockaddr。如果 address 資料是用 sockaddr_in 儲存,呼叫這些 API 時就需要強制轉型。
同樣地,如果使用 socket 的場景不是網路,也會有對應的結構存放地址(例如 sockaddr_un 對應 Unix domain socket)。這些不同的結構在呼叫通用 API 時,都透過強制轉型成 sockaddr 來實現統一介面。
實際範例: 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));