前言
我們深諳信息交流的價(jià)值,那網(wǎng)絡(luò)中進(jìn)程之間如何通信,如我們每天打開瀏覽器瀏覽網(wǎng)頁時(shí),瀏覽器的進(jìn)程怎么與web服務(wù)器通信的?當(dāng)你用QQ聊天時(shí),QQ進(jìn)程怎么與服務(wù)器或你好友所在的QQ進(jìn)程通信?這些都得靠socket?那什么是socket?socket的類型有哪些?還有socket的基本函數(shù),這些都是本文想介紹的。
1 網(wǎng)絡(luò)中進(jìn)程之間如何通信
本地的進(jìn)程間通信(IPC)有很多種方式,但可以總結(jié)為下面4類:
1)消息傳遞(管道、FIFO、消息隊(duì)列)
2)同步(互斥量、條件變量、讀寫鎖、文件和寫記錄鎖、信號量)
3)共享內(nèi)存(匿名的和具名的)
4)遠(yuǎn)程過程調(diào)用(Solaris門和Sun RPC)
但這些都不是本文的主題!我們要討論的是網(wǎng)絡(luò)中進(jìn)程之間如何通信?首要解決的問題是如何唯一標(biāo)識一個(gè)進(jìn)程,否則通信無從談起!在本地可以通過進(jìn)程PID來唯一標(biāo)識一個(gè)進(jìn)程,但是在網(wǎng)絡(luò)中這是行不通的。其實(shí)TCP/IP協(xié)議族已經(jīng)幫我們解決了這個(gè)問題,網(wǎng)絡(luò)層的“ip地址”可以唯一標(biāo)識網(wǎng)絡(luò)中的主機(jī),而傳輸層的“協(xié)議+端口”可以唯一標(biāo)識主機(jī)中的應(yīng)用程序(進(jìn)程)。這樣利用三元組(ip地址,協(xié)議,端口)就可以標(biāo)識網(wǎng)絡(luò)的進(jìn)程了,網(wǎng)絡(luò)中的進(jìn)程通信就可以利用這個(gè)標(biāo)志與其它進(jìn)程進(jìn)行交互。
使用TCP/IP協(xié)議的應(yīng)用程序通常采用應(yīng)用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經(jīng)被淘汰),來實(shí)現(xiàn)網(wǎng)絡(luò)進(jìn)程之間的通信。就目前而言,幾乎所有的應(yīng)用程序都是采用socket,而現(xiàn)在又是網(wǎng)絡(luò)時(shí)代,網(wǎng)絡(luò)中進(jìn)程通信是無處不在,這就是我為什么說“一切皆socket”。
2 什么是Socket
上面我們已經(jīng)知道網(wǎng)絡(luò)中的進(jìn)程是通過socket來通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲學(xué)之一就是“一切皆文件”,都可以用“打開open –> 讀寫read /write –> 關(guān)閉close”模式來操作。我的理解就是Socket就是該模式的一個(gè)實(shí)現(xiàn),socket即是一種特殊的文件,一些socket函數(shù)就是對其進(jìn)行的操作(讀/寫IO、打開、關(guān)閉),這些函數(shù)我們在后面進(jìn)行介紹。
socket一詞的起源
在組網(wǎng)領(lǐng)域的首次使用是在1970年2月12日發(fā)布的文獻(xiàn)IETF RFC33中發(fā)現(xiàn)的,撰寫者為Stephen Carr、Steve Crocker和Vint Cerf。根據(jù)美國計(jì)算機(jī)歷史博物館的記載,Croker寫道:“命名空間的元素都可稱為套接字接口。一個(gè)套接字接口構(gòu)成一個(gè)連接的一端,而一個(gè)連接可完全由一對套接字接口規(guī)定。”計(jì)算機(jī)歷史博物館補(bǔ)充道:“這比BSD的套接字接口定義早了大約12年。”
3 socket的基本操作
既然socket是“open—read/write—close”模式的一種實(shí)現(xiàn),那么socket就提供了這些操作對應(yīng)的函數(shù)接口。下面以TCP為例,介紹幾個(gè)基本的socket接口函數(shù)。
3.1 socket()函數(shù)
int socket(int domain, int type, int protocol);
socket函數(shù)對應(yīng)于普通文件的打開操作。普通文件的打開操作返回一個(gè)文件描述字,而socket()用于創(chuàng)建一個(gè)socket描述符(socket descriptor),它唯一標(biāo)識一個(gè)socket。這個(gè)socket描述字跟文件描述字一樣,后續(xù)的操作都有用到它,把它作為參數(shù),通過它來進(jìn)行一些讀寫操作。
正如可以給fopen的傳入不同參數(shù)值,以打開不同的文件。創(chuàng)建socket的時(shí)候,也可以指定不同的參數(shù)創(chuàng)建不同的socket描述符,socket函數(shù)的三個(gè)參數(shù)分別為:
1)domain:即協(xié)議域,又稱為協(xié)議族(family)。常用的協(xié)議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協(xié)議族決定了socket的地址類型,在通信中必須采用對應(yīng)的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個(gè)絕對路徑名作為地址。
2)type:指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的類型有哪些?)。
3)protocol:顧名思義,就是指定協(xié)議。常用的協(xié)議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應(yīng)TCP傳輸協(xié)議、UDP傳輸協(xié)議、STCP傳輸協(xié)議、TIPC傳輸協(xié)議。
注意:并不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當(dāng)protocol為0時(shí),會自動選擇type類型對應(yīng)的默認(rèn)協(xié)議。
當(dāng)我們調(diào)用socket創(chuàng)建一個(gè)socket時(shí),返回的socket描述字它存在于協(xié)議族(address family,AF_XXX)空間中,但沒有一個(gè)具體的地址。如果想要給它賦值一個(gè)地址,就必須調(diào)用bind()函數(shù),否則就當(dāng)調(diào)用connect()、listen()時(shí)系統(tǒng)會自動隨機(jī)分配一個(gè)端口。
3.2 bind()函數(shù)
正如上面所說bind()函數(shù)把一個(gè)地址族中的特定地址賦給socket。例如對應(yīng)AF_INET、AF_INET6就是把一個(gè)ipv4或ipv6地址和端口號組合賦給socket。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數(shù)的三個(gè)參數(shù)分別為:
1)sockfd:即socket描述字,它是通過socket()函數(shù)創(chuàng)建,唯一標(biāo)識一個(gè)socket。bind()函數(shù)就是給這個(gè)描述字綁定一個(gè)名字。
2)addr:一個(gè)const struct sockaddr *指針,指向要綁定給sockfd的協(xié)議地址。這個(gè)地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket時(shí)的地址協(xié)議族的不同而不同,例如,
ipv4對應(yīng)的是:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};