在有了前一章的基礎知識後,我想我們該進入下一步的學習了。
一、socket的說明
socket是使用 Unix 檔案描述符 (fiel descriptor) 和其他程式通訊的方式。 Unix 程式在執行任何形式的 I/O 的時候, 程式是在讀或者寫一個檔案描述符。一個檔案描述符只是一個和開啟的檔案相關聯的整數。 但是(注意後面的話),這個檔案可能是一個網路連接,FIFO,管道,終端,磁碟上的檔案 或者什麼其他的東西。Unix 中所有的東西是檔案!因此,你想和 Internet 上別的程式通訊的時候,你將要通過檔案描述符。如果它是個檔案描述符,那麼為什麼不用一般的調用 read() 和 write() 來通過套介面通訊?”簡單的答案是:“你可以使用一般的函數!”。詳細的答案是:“你可以,但是使用 send() 和 recv() 讓你更好的控制資料轉送。”
二、Internet套介面的兩種類型
一種是 "Stream Sockets",另外一種是 "Datagram Sockets"。我們以後談到他們的時候也會用到 "SOCK_STREAM" 和 "SOCK_DGRAM"。
流式套介面是可靠的雙向通訊的資料流。如果你向套介面安順序輸出“1,2”,那麼他們 將安順序“1,2”到達另一邊。他們也是無錯誤的傳遞的,有自己的錯誤控制。
資料報也使用 IP 作路由,但是他不選擇 TCP。他使用“使用者資料包通訊協定 (User Datagram Protocol)”。
三、網路理論知識
網路分層模型可以分為應用程式層 (Application) 展示層 (Presentation) 會話層 (Session) 傳輸層 (Transport) 網路層 (Network) 資料連結層 (Data Link) 物理層 (Physical) 這幾個層,其實他們就像穿衣脫衣一樣,當你從你主機上向其他主機發送一個訊息的時候,就開始不斷的穿衣,等在網路傳輸到其他主機時,又開始脫衣,然後其他主機就解讀到了你發送的訊息。這裡不做太多的講解。
四、網路編程常用結構體
第一個結構(TM)--struct sockaddr. 這個資料結構 為許多類型的套介面儲存套介面地址資訊: struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
sa_family 能夠是各種各樣的事情,但是在這篇文章中是 "AF_INET"。 sa_data 為套介面儲存目標地址和
連接埠資訊。
為了對付 struct sockaddr,程式員創造了一個並列的結構: struct sockaddr_in ("in" 代表 "Internet".)
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
這個資料結構讓可以輕鬆處理套介面地址的基本元素。注意 sin_zero (他 被加入到這個結構,並且長度和 struct sockaddr 一樣) 應該使用函數 bzero() 或 memset() 來全部置零。即使 socket() 想要的是 struct sockaddr *, 你仍然可以使用 struct sockaddr_in同時,注意 sin_family 和 struct sockaddr 中的 sa_family 一致並能夠設定為 "AF_INET"。最後, sin_port 和 sin_addr 必須是網路位元組順序 (Network Byte Order)。
你也許會反對道:"但是,怎麼讓整個資料結構 struct in_addr sin_addr 按照網路位元組順序呢?" 要知道這個問題的答案,我們就要仔細的看一 看這個資料結構: struct in_addr, 有這樣一個聯合 (unions):
/* Internet address (a structure for historical reasons) */
struct in_addr {
unsigned long s_addr;
};
它曾經是個最壞的聯合,但是現在那些日子過去了。如果你聲明 "ina" 是 資料結構 struct sockaddr_in 的執行個體,那麼 "ina.sin_addr.s_addr" 就儲存4位元組的 IP 位址(網路位元組順序)。如果你不幸的 系統使用的還是恐怖的聯合 struct in_addr ,你還是可以放心4字 節的 IP 位址是和上面我說的一樣(這是因為 #define。)
五、Convert the Natives(本機轉換)
我們又開始學習新的知識,也許聽說過不同的主機可能在機器中儲存資訊的位元組順序是不一樣的,這樣我們在網路傳輸中也會遇到這樣的問題,那麼就需要我們對網路到本機和本機到網路位元組順序的轉換。你能夠轉換兩種類型:short(兩個位元組)和long(四個位元組)。如果你想將short從本機位元組順序轉換為網路位元組順序,用"h"表示本機("host"),接著是"to",然後用"n"表示網路("nerwork"),最後用"s"表示"short":h-to-n-s或者htons()。當然這樣我可以想到還有其他的,如下:
htons() --"host to network short"
htonl() --"host to network long"
ntohs() --"network to host short"
ntohl() --"network to host long"
現在,你可能認為你已經知道它們了,也許你會想到你的主機上已經是使用了網路位元組順序,沒有必要去使用htonl()轉換IP地址。這個你沒有錯,可是程式將要移植到其他主機上去呢,那就可能存在著隱患了。所以記住:在你將資料放到網路上的時候,確信它們是網路位元組順序的。
最後一點,為什麼在資料結構struct sockaddr_in中,sin_addr和sin_port需要轉換為網路位元組順序,而sin_family是不是也需要呢?sin_addr和sin_port分別封裝在包IP和UDP層,因此它們必須是網路位元組順序。但是sin_family域只是被核心使用來決定在資料結構中包含什麼類型的地址,所以它必須是本機位元組順序。同時,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地址。例如:
char *a1, *a2;
.
.
a1 = inet_ntoa(ina1.sin_addr); /* 這是198.92.129.1 */
a2 = inet_ntoa(ina2.sin_addr); /* 這是132.241.5.10 */
printf("address 1: %sn",a1);
printf("address 2: %sn",a2);
輸出如下:
address 1: 132.241.5.10
address 2: 132.241.5.10
假如你需要儲存這個IP地址,使用strcopy()函數來指向你自己的字元指標。