1 Socket API簡介
1.1 最初設(shè)計(jì)
最初設(shè)計(jì)是基于BSD UNIX-Berkley,面向TCP/IP協(xié)議棧的接口。經(jīng)過后續(xù)的發(fā)展,已經(jīng)可以面向多個(gè)協(xié)議棧。Socket API也是事實(shí)上的工業(yè)標(biāo)準(zhǔn),絕大多數(shù)的操作系統(tǒng)都支持,主流的操作系統(tǒng)如Windows和Linux都是支持的。Windows和Linux操作系統(tǒng)下Socket API的功能是有重疊的,基本編程思路不變,只需改動極小量的代碼即可在相應(yīng)操作系統(tǒng)編譯、鏈接、運(yùn)行。
1.2 形象比喻
首先,Socket API是Internet網(wǎng)絡(luò)應(yīng)用最典型的API接口,使用的通信模型為客戶/服務(wù)器模型(C/S),這類模型描述了應(yīng)用進(jìn)程間通信的抽象機(jī)制。
我們可以將socket形象比喻成插頭與插座,現(xiàn)在你想要給自己的手機(jī)充電,需要什么東西才能通過插座給手機(jī)充電呢?那就是適配手機(jī)的充電線和充電插頭,在此我們把它們看成一個(gè)整體,叫做充電工具。現(xiàn)在你只需要將充電工具的USB插口一端與手機(jī)連接,另一端插頭插到插座上,手機(jī)就能夠進(jìn)行充電,我們無須了解插座后面的電路構(gòu)造,只要將插頭插入插座就能使用充電功能,這就是API的現(xiàn)實(shí)體現(xiàn)。
1.3 通信定位
在一臺主機(jī)上可能運(yùn)行多個(gè)進(jìn)程,比如現(xiàn)在打開的簡書Web應(yīng)用或者QQ,假設(shè)突然有個(gè)套接字要過來和你的進(jìn)程通信,那現(xiàn)在主機(jī)上那么多進(jìn)程,它應(yīng)該和哪個(gè)進(jìn)程通信?換句話說,它應(yīng)該如何找到指定的進(jìn)程進(jìn)行通信?再形象點(diǎn),插頭要插哪個(gè)插座上?我們要如何準(zhǔn)確定位服務(wù)器端的套接字呢?
其實(shí)很簡單,在某一特定網(wǎng)絡(luò)中,一臺主機(jī)對應(yīng)唯一的IP地址,服務(wù)器其實(shí)也是網(wǎng)絡(luò)上的一臺主機(jī),也對應(yīng)一個(gè)IP地址,這其實(shí)就能找到主機(jī)的地址。以五層模型為參考,主機(jī)間應(yīng)用層的交互實(shí)際上是通過底層傳輸層協(xié)議來進(jìn)行交互,操作系統(tǒng)需要提供端口來給進(jìn)程,即端口號。
對外(主機(jī)與主機(jī)間)來說,套接字通過IP地址+端口號來標(biāo)識通信端點(diǎn),這樣的標(biāo)識是唯一的。對內(nèi)(主機(jī)本身)來說,操作系統(tǒng)使用套接字描述符(socket descriptor)來管理套接字,大小一般是小整數(shù)。由此可見,套接字對內(nèi)對外管理是不一樣的。
- 需要標(biāo)識通信端點(diǎn)(對外):IP地址+Port端口號→套接字的端點(diǎn)地址
(1)IP地址標(biāo)識互聯(lián)網(wǎng)中的機(jī)器地址。
(2)Port端口號為16位整數(shù),標(biāo)識某一具體機(jī)器的進(jìn)程,綁定套接字。 - 操作系統(tǒng)/進(jìn)程管理套接字(對內(nèi)):使用套接字描述符(socket descriptor)。
2 socket抽象
2.1 socket創(chuàng)建
- 在Unix、Linux操作系統(tǒng)中,將socket看作文件。
- 當(dāng)應(yīng)用進(jìn)程創(chuàng)建套接字時(shí),操作系統(tǒng)分配一個(gè)數(shù)據(jù)結(jié)構(gòu)存儲該套接字相關(guān)信息。
- 返回套接字描述符。
2.2 socket管理
每個(gè)進(jìn)程管理一個(gè)socket描述符表,表中的入口都有一個(gè)指針,指向所存儲的數(shù)據(jù)結(jié)構(gòu)。
| socket描述符表 | socket數(shù)據(jù)結(jié)構(gòu) |
|---|---|
| [0] | struct sockaddr_in * |
| [1] | 其他數(shù)據(jù)結(jié)構(gòu) |
| [2] | 其他數(shù)據(jù)結(jié)構(gòu) |
| ... | 其他數(shù)據(jù)結(jié)構(gòu) |
作為套接字最重要的信息就是套接字的地址信息,使用前需要指定本地的端點(diǎn)地址和遠(yuǎn)程的端點(diǎn)地址。使用TCP/IP協(xié)議簇的網(wǎng)絡(luò)應(yīng)用程序聲明端點(diǎn)地址變量時(shí),通過結(jié)構(gòu)sockaddr_in來描述地址信息,使用前需要include <netinet/in.h>。
struct sockaddr_in{
u_char sin_len; // 地址長度
u_char sin_family; // 地址族(TCP/IP:AF_INET) 面向各種不同的協(xié)議棧,主要是TCP
u_short sin_port; // 端口號
struct in_addr sin_addr; // IP地址
char sin_zero[8]; // 未用(置0) 填充0以保持與sockaddr結(jié)構(gòu)的長度相同
}
不同地址族(協(xié)議棧)的端點(diǎn)地址是不一樣的,TCP/IP協(xié)議簇的端點(diǎn)地址為IP+Port,通常指定其sin_family數(shù)據(jù)成員為AF_INET符號常量,表示本身為TCP/IP協(xié)議簇。