什麼是 socket?
你經常聽到人們談論著
"socket",或許你還不知道它的確切含義。現在讓我告訴你:它是使用 標準Unix
檔案描述符 (file descriptor) 和其它程式通訊的方式。
什嗎?
struct sockaddr.。這個結構 為許多類型的通訊端儲存通訊端地址資訊:
struct sockaddr {
unsigned short sa_family; /* 地址家族, AF_xxx */
char sa_data[14]; /*14位元組協議地址*/
};
sa_family 能夠是各種各樣的類型,但是在這篇文章中都是 "AF_INET"。
sa_data包含通訊端中的目標地址和連接埠資訊。這好像有點 不明智。
為了處理struct sockaddr,程式員創造了一個並列的結構: struct sockaddr_in
("in" 代表 "Internet"。)
struct sockaddr_in {
short int sin_family; /* 通訊類型 */
unsigned short int sin_port; /* 連接埠 */
struct in_addr sin_addr; /* 網際網路位址 */
unsigned char sin_zero[8]; /* 與sockaddr結構的長度相同*/
};
用這個資料結構可以輕鬆處理通訊端地址的基本元素。
一個指向 sockaddr_in結構體的指標也可以被指向結構體sockaddr並且代替它。這
樣的話即使 socket() 想要的是 struct sockaddr *,你仍然可以使用 struct
sockaddr_in,並且在最後轉換。同時,注意 sin_family 和 struct sockaddr 中的
sa_family 一致並能夠設定為 "AF_INET"。最後,sin_port和 sin_addr
必須是網路位元組順序 (Network Byte Order)!
htons()--"Host to Network Short"
htonl()--"Host to Network Long"
ntohs()--"Network to Host Short"
ntohl()--"Network to Host Long"
為什麼在資料結構 struct sockaddr_in 中, sin_addr 和 sin_port
需要轉換為網路位元組順序,而sin_family 需不需要呢? 答案是: sin_addr 和
sin_port 分別封裝在包的 IP 和 UDP 層。因此,它們必須要
是網路位元組順序。但是 sin_family 域只是被核心 (kernel) 使用來決定在數
據結構中包含什麼類型的地址,所以它必須是本機位元組順序。同時, sin_family
沒有發送到網路上,它們可以是本機位元組順序。
IP 位址和如何處理它們
現在我們很幸運,因為我們有很多的函數來方便地操作 IP 位址。沒有
必要用手工計算它們,也沒有必要用"<<"操作來儲存成長整字型。
首先,假設你已經有了一個sockaddr_in結構體ina,你有一個IP地
址"132.241.5.10"要儲存在其中,你就要用到函數inet_addr(),將IP地址從
點數格式轉換成無符號長整型。使用方法如下:
ina.sin_addr.s_addr = inet_addr("132.241.5.10");
注意,inet_addr()返回的地址已經是網路位元組格式,所以你無需再調用
函數htonl()。
我們現在發現上面的代碼片斷不是十分完整的,因為它沒有錯誤檢查。
顯而易見,當inet_addr()發生錯誤時返回-1。記住這些位元字?(無符
號數)-1僅僅和IP地址255.255.255.255相符合!這可是廣播位址!大錯特
錯!記住要先進行錯誤檢查。
好了,現在你可以將IP地址轉換成長整型了。有沒有其相反的方法呢?
它可以將一個in_addr結構體輸出成點數格式?這樣的話,你就要用到函數
inet_ntoa()("ntoa"的含義是"network to ascii"),就像這樣:
printf("%s",inet_ntoa(ina.sin_addr));
它將輸出IP地址。需要注意的是inet_ntoa()將結構體in-addr作為一
個參數,不是長整形。同樣需要注意的是它返回的是一個指向一個字元的
指標。它是一個由inet_ntoa()控制的靜態固定的指標,所以每次調用
inet_ntoa(),它就將覆蓋上次調用時所得的IP地址。