大綱 一.Socket簡介 二.BSD Socket編程準備 1.地址 2.連接埠 3.網路位元組序 4.半相關與全相關 5.網路編程模型 三.socket介面編程樣本 四.使用select 五.使用kqueue 六.使用流
一.Socket簡介
在UNIX系統中,萬物皆檔案(Everything is a file)。所有的IO操作都可以看作對檔案的IO操作,都遵循著這樣的操作模式:開啟 -> 讀/寫 -> 關閉,開啟操作(如open函數)擷取“檔案”使用權,返迴文件描述符,後繼的操作都通過這個檔案描述符來進行。很多系統調用都依賴於檔案描述符,它是一個不帶正負號的整數,每一個使用者進程都對應著一個檔案描述符表,通過檔案描述符就可以找到對應檔案的資訊。 在類UNIX平台上,對於控制台的標準輸入輸出以及標準錯誤輸出都有對應的檔案描述符,分別為0,1,2。它們定義在 unistd.h中 [cpp] view plain copy print ? #define STDIN_FILENO 0 /* standard input file descriptor */ #define STDOUT_FILENO 1 /* standard output file descriptor */ #define STDERR_FILENO 2 /* standard error file descriptor */
在Mac系統中,可以通過Activity Monitor來查看某個進程開啟的檔案和連接埠。
UNIX核心加入TCP/IP協議的時候,便在系統中引入了一種新的IO操作,只不過由於網路連接的不可靠性,所以網路IO比本地裝置的IO複雜很多。這一系列的介面叫做BSD Socket API,當初由伯克利大學研發,最終成為網路開發介面的標準。 網路通訊從本質上講也是處理序間通訊,只是這兩個進程一般在網路中不同電腦上。當然Socket API其實也提供了專門用於本地IPC的使用方式:UNIX Domain Socket,這個這裡就不細說了。本文所講的Socket如無例外,均是說的Internet Socket。
在本地的進程中,每一個進程都可以通過PID來標識,對於網路上的一個電腦中的進程如何標識呢。網路中的電腦可以通過一個IP地址進行標識,一個電腦中的某個進程則可以通過一個不帶正負號的整數(連接埠號碼)來標識,所以一個網路中的進程可以通過IP地址+連接埠號碼的方式進行標識。
二.BSD Socket編程準備
1.地址
在程式中,我們如何儲存一個地址呢。在 <sys/socket.h>中的sockaddr便是描述socket地址的結構體類型. [cpp] view plain copy print ? /* * [XSI] Structure used by kernel to store most addresses. */ struct sockaddr { __uint8_t sa_len; /* total length */ sa_family_t sa_family; /* [XSI] address family */ char sa_data[14]; /* [XSI] addr value (actually larger) */ };
為了方便設定用語網路通訊的socket地址,引入了sockaddr_in結構體(對於UNIX Domain Socket則對應sockaddr_un) [cpp] view plain copy print ? /* * Socket address, internet style. */ struct sockaddr_in { __uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port;//得是網路位元組序 struct in_addr sin_addr;//in_addr存在的原因則是曆史原因,其實質是代表一個IP地址的32位整數 char sin_zero[8];//bzero之,純粹是為了相容sockaddr };
在實際編程的時候,經常需要將sockaddr_in強制轉換成sockaddr類型。
2.連接埠
說到連接埠我們經常會聯想到硬體,在網路編程中的連接埠其實是一個標識而已,或者說是系統的資源而已。系統提供了連接埠分配和管理的機制。
3.網路位元組序
談網路位元組序(Endianness)之前我們先說說什麼是位元組序。位元組序又叫端序,就是指電腦中存放 多位元組資料的位元組的順序。典型的就是資料存放在記憶體中或者網路傳輸時的位元組的順序。常用的位元組序有大端序(big-endian),小端序(litle-endian,另還有不常見的混合序middle-endian)。不同的CPU可能會使用不同的位元組序,如X86,PDP-11等處理器為小端序,Motorola 6800,PowerPC 970等使用的是大端序。小端序是指低位元組位存放在記憶體位址的低端,高端序是指高位位元組存放在記憶體的低端。 舉個例子來說明什麼是大端序和小端序: 比如一個4位元組的整數 16進位形式為 0x12345678,最左邊是高位。
大端序
低位 |
> > |
> > |
高位 |
12 |
34 |
56 |
78 |
小端序
低位 |
> > |
> > |
高位 |
78 |
56 |
34 |
12 |
TCP/IP 各層協議將位元組序使用的是大端序,我們把TCP/IP協議中使用的位元組序稱之為網路位元組序。 編程的時候可以使用定義在sys/_endian.h中的相關的介面進行本地位元組序和網路位元組序的互轉。 [cpp] view plain copy print ? #define ntohs(x) __DARWIN_OSSwapInt16(x) // 16位整數 網路位元組序轉主機位元組序 #define htons(x) __DARWIN_OSSwapInt16(x) // 16位整數 主機位元組序轉網路位元組序