Socket 介紹
概述
socket是一種IPC方法辩块,它允許位于同一主機(jī)或使用網(wǎng)絡(luò)連接起來的不同主機(jī)的應(yīng)用程序之間交換數(shù)據(jù)锉走。
socket進(jìn)行通信的方式如下:
- 各個應(yīng)用程序創(chuàng)建一個socket以清,socket是一個允許通信的“設(shè)備”弥鹦,兩個應(yīng)用程序都需要用到它戏挡。
- 服務(wù)器將自己的socket綁定到一個眾所周知的地址上使得客戶端能夠定位到它的位置固逗。
使用socket()系統(tǒng)調(diào)用能夠創(chuàng)建一個socket,它返回一個用來在后續(xù)系統(tǒng)調(diào)用中引用該socket的文件描述符拘领。
fd = socket(domain, type, protocol)
通信domain
socket存在于一個通信domain中淋纲,它確定:
- 識別出一個socket的方法(即socket“地址”的格式)
- 通信范圍(是同一主機(jī),還是網(wǎng)絡(luò)中不同主機(jī))
現(xiàn)在操作系統(tǒng)支持下列domain:
- UNIX(AF_UNIX)domain:允許同一主機(jī)上的應(yīng)用程序之間進(jìn)行通信
- IPv4(AF_INET)domain
- IPv6(AF_INET6)domain
socket類型
每個socket實(shí)現(xiàn)都至少提供了兩種socket:流和數(shù)據(jù)報院究。這兩種類型在UNIX和Internet domain中都得到了支持洽瞬。
流socket(SOCK_STREAM)
流socket提供了一個可靠的雙向的字節(jié)流通信信道:
- 可靠的
- 雙向的
- 字節(jié)流
數(shù)據(jù)報socket(SOCK_DGRAM)
數(shù)據(jù)報socket允許數(shù)據(jù)以數(shù)據(jù)報的形式進(jìn)行交換。在使用時無需與另一個socket簡歷連接业汰。
socket 系統(tǒng)調(diào)用
socket()
int socket(domain, type, protocol)
- 創(chuàng)建一個新的socket
- type 指定socket類型伙窃,創(chuàng)建流socket通常指定為 SOCK_STREAM,數(shù)據(jù)報socket指定為SOCK_DGRAM
- protocol默認(rèn)為0
- 返回socket的文件描述符
bind()
int bind(sockfd, struct sockaddr *addr, socklen_t addrlen)
- 將一個socket綁定到一個地址上样漆,通常为障,服務(wù)器需要使用這個調(diào)用來將其socket綁定到一個地址上供客戶端能夠定位到該socket上
- addr參數(shù)是一個指針,指向socket綁定到的地址結(jié)構(gòu)上放祟,這個參數(shù)的結(jié)構(gòu)類型取決于domain鳍怨。
- 服務(wù)器可以不調(diào)用bind()直接調(diào)用listen(),這將會導(dǎo)致內(nèi)核為該socket選擇一個臨時端口
- 客戶端一般不需要調(diào)用跪妥,操作系統(tǒng)會自動綁定
通用socket地址結(jié)構(gòu):struct sockaddr
傳入bind()的addr比較復(fù)雜鞋喇,每種socket domain都使用了不同的地址格式,如UNIX domain socket使用路徑名眉撵,而Internet domain socket 使用IP地址和端口號侦香。struct sockaddr適用于所有domain,將各種domain特定的地址結(jié)構(gòu)轉(zhuǎn)換成單個類型以供socket系統(tǒng)調(diào)用中的各個參數(shù)使用纽疟。
listen()
- 允許一個流socket接受來自其他socket的接入連接
accept()
- 在一個監(jiān)聽流socket上接受來自一個對等應(yīng)用程序的連接罐韩,并可選的返回對等socket地址
connect()
- 簡歷與另一個socket之間的連接
在大多數(shù)Linux架構(gòu)上污朽,所有這些socket系統(tǒng)調(diào)用實(shí)際上被實(shí)現(xiàn)成了通過單個系統(tǒng)調(diào)用socketcall()進(jìn)行多路復(fù)用的庫函數(shù)。
socket I/O 可以使用傳統(tǒng)的read()和write()系統(tǒng)調(diào)用或使用一組socket特有的系統(tǒng)調(diào)用send() recv() sendto() recvfrom()矾睦。默認(rèn)情況下晦款,這些系統(tǒng)調(diào)用在I/O操作無法被立即完成時阻塞,使用fcntl() F_SETFL 操作用啟用 O_NONBLOCK 打開文件狀態(tài)標(biāo)記可以執(zhí)行非阻塞I/O
流socket
流程
- 每個應(yīng)用程序都必須創(chuàng)建一個socket
- 服務(wù)端程序調(diào)用bind()將socket綁定到一個地址上柬赐,然后調(diào)用listen()通知內(nèi)核它接受接入連接的意愿官紫。
- 其他客戶端程序通過connect()建立連接,同時指定需連接的socket的地址
- 服務(wù)端程序使用accept()接受連接州藕。
- 建立連接后束世,進(jìn)行雙向數(shù)據(jù)傳輸直到其中一個使用close()關(guān)閉連接為止床玻。
- 通信是通過傳統(tǒng)的read()和write()系統(tǒng)調(diào)用或通過額外的一些socketAPI(send() recv())
監(jiān)聽接入連接:listen()
int listen(sockfd, backlog)
listen()系統(tǒng)調(diào)用將文件描述符sockfd引用的流socket標(biāo)記為被動锈死,這個socket后面會被用來接受來自其他(主動的)socket的鏈接。
無法再一個已連接的socket(已成功執(zhí)行connect()的socket或由accept()調(diào)用返回的socket)上執(zhí)行 listen()
如果服務(wù)器正忙于處理其他客戶端其屏,那么客戶端的connect()可能并不能馬上被accept()缨该,這將產(chǎn)生一個未決的連接贰拿。
內(nèi)核必須要記錄所有未決的連接請求的相關(guān)信息,backlog參數(shù)允許限制這種未決連接的數(shù)量妙真。在這個限制之內(nèi)的連接請求會立即成功荚守,之外的連接請求就會阻塞直到一個未決的連接被接受,并從未決連接隊(duì)列中刪除健蕊。
接受連接:accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
accept()系統(tǒng)調(diào)用會文件描述符sockfd引用的監(jiān)聽流socket上接受一個連入連接缩功。如果在調(diào)用accept時不存在未決的連接,那么調(diào)用會阻塞直到有連接請求到達(dá)為止虑稼。
返回的結(jié)果是已連接的socket的文件描述符。addr參數(shù)指向一個用來返回socket地址的結(jié)構(gòu)歌懒。
連接到對等socket:connect()
int connect()
流socket I/O
一對連接的流 socket 在兩個端點(diǎn)之間提供了一個雙向通信信道溯壶。
連接終止:close()
關(guān)閉一個連接之后且改,對等應(yīng)用程序讀取數(shù)據(jù)時將會收到文件結(jié)束(所有緩沖數(shù)據(jù)都讀取之后),如果要寫入數(shù)據(jù)碍拆,會收到一個SIGPIPE信號感混,并且系統(tǒng)調(diào)用返回EPIPE錯誤礼烈。
數(shù)據(jù)報 socket
流程
- 需要發(fā)送和接受數(shù)據(jù)報的應(yīng)用程序都需要使用socket()創(chuàng)建一個數(shù)據(jù)報socket
- 服務(wù)器將socket綁定到IP地址上,客戶端通過該地址發(fā)起通信谱秽。
- 要發(fā)送一個數(shù)據(jù)報摹迷,客戶端調(diào)用sendto()
- 服務(wù)端調(diào)用recvfrom()峡碉,在沒有數(shù)據(jù)報時會阻塞。
- 不再需要是吉执,調(diào)用close()
無法保證順序地来,也無法保證能夠到達(dá)未斑。由于底層協(xié)議有時會重新傳包,也可能多次到達(dá)府阀。
交換數(shù)據(jù)報 recvfrom() sendto()
在數(shù)據(jù)報socket上使用connect()
盡管數(shù)據(jù)報socket是無連接的试浙,但在數(shù)據(jù)報socket上應(yīng)用connect()系統(tǒng)調(diào)用仍然起作用,會導(dǎo)致內(nèi)核記錄這個socket的對等socket地址钠糊。
當(dāng)一個數(shù)據(jù)報socket已連接后:
- 數(shù)據(jù)報的發(fā)送可在socket上使用write和send
Socket UNIX DOMAIN
UNIX domain socket地址:struct sockaddr_un
在UNIX domain中固额,socket地址以路徑名來表示斗躏,domain特定的socket地址結(jié)構(gòu)的定義如下:
為將一個UNIX domain socket綁定到一個地址上啄糙,需要初始化一個sockaddr_un結(jié)構(gòu)云稚,然后將指向這個結(jié)構(gòu)的一個指針作為addr參數(shù)傳入bind()并將addrlen指定為這個結(jié)構(gòu)的大小。
當(dāng)用來綁定UNIX domain socket時燕雁,bind()會在文件系統(tǒng)中創(chuàng)建一個條目拐格,作為socket路徑名的一部分的目錄需要可訪問和可寫刑赶。這個文件會被標(biāo)記為一個socket,當(dāng)再這個路徑名上應(yīng)用stat()時金踪,它會在stat結(jié)構(gòu)的st_mode字段中的文件類型部分返回值S_IFSOCK胡岔。
盡管UNIX domain socket是通過路徑名來標(biāo)識的枷餐,但這些socket上發(fā)生的I/O無須對底層設(shè)備進(jìn)行操作。
有關(guān)綁定一個UNIX domain socket的注意點(diǎn):
- 無法將一個socket綁定到一個既有路徑名上
- 通常將一個socket綁定到一個絕對路徑上奕锌,這樣這個socket就會位于文件系統(tǒng)中的一個固定地址處
- 一個socket只能綁定一個路徑名
- 無法使用open()打開一個socket
- 當(dāng)不再需要一個socket時可以使用unlink()刪除其路徑條目
UNIX domain中的流socket
服務(wù)器流程:
- 創(chuàng)建一個socket
- 刪除所有與路徑名一致的既有文件惊暴,這樣就能將socket綁定到這個路徑名上
- 為服務(wù)器socket構(gòu)建一個地址結(jié)構(gòu),將socket綁定到該地址上肄鸽,將這個socket標(biāo)記為監(jiān)聽socket
- 執(zhí)行一個無線循環(huán)來處理進(jìn)入的客戶請求油啤,每次循環(huán)迭代執(zhí)行以下任務(wù):
- 接受一個連接益咬,為該連接獲取一個新的socket cfd
- 從已連接的socket中讀取所有數(shù)據(jù)并將這些數(shù)據(jù)寫入到標(biāo)準(zhǔn)輸出中
- 關(guān)閉已連接的socket cfd
- 服務(wù)器手動終止(發(fā)送一個信號)
客戶端流程:
- 創(chuàng)建一個socket
- 為服務(wù)器socket構(gòu)建一個地址并連接到位于該地址處的socket
- 執(zhí)行一個循環(huán),將其標(biāo)準(zhǔn)輸入復(fù)制到socket連接上梅鹦,當(dāng)遇到標(biāo)準(zhǔn)輸入中的文件結(jié)尾時客戶端就終止齐唆,其結(jié)果是客戶端socket將會關(guān)閉并且服務(wù)器從連接的另一端的socket中讀取數(shù)據(jù)時會看到文件結(jié)束冻河。
UNIX domain中的數(shù)據(jù)報socket
對于UNIX domain socket來說叨叙,數(shù)據(jù)報的傳輸是在內(nèi)核中發(fā)生的,也是可靠的廷蓉,所有消息都會按序被遞送并且不會發(fā)生重復(fù)的狀況马昙。
服務(wù)器創(chuàng)建socket后并綁定后,進(jìn)入一個無線循環(huán)攒暇,在循環(huán)中使用recvfrom()接收來自客戶端的數(shù)據(jù)報形用,將接收到的文本轉(zhuǎn)換成大小格式并使用通過recvfrom()獲取的地址將轉(zhuǎn)換過的文本返回給客戶端。
UNIX domain socket權(quán)限
socket文件的所有權(quán)和權(quán)限決定了哪些進(jìn)程能夠與這個socket進(jìn)行通信
- 要連接一個UNIX domain流socket需要在該socket文件上擁有寫權(quán)限
- 要通過一個UNIX domain數(shù)據(jù)報socket發(fā)送一個數(shù)據(jù)報需要在該socket文件上擁有寫權(quán)限
- 需要在存放socket路徑名的所有目錄上都擁有執(zhí)行權(quán)限
創(chuàng)建互聯(lián)socket對:socketpair()
有時候讓單個進(jìn)程創(chuàng)建一對socket并將它們連接起來是比較有用的妒御。
int socketpair(int domain, int type, int protocol, int sockfd[2])
Linux抽象socket名空間
允許將一個UNIX domain socket綁定到一個名字上但不會在文件系統(tǒng)中創(chuàng)建的名字
- 無需擔(dān)心與文件系統(tǒng)中的既有名字產(chǎn)生沖突
- 沒有必要在使用完socket之后刪除socket路徑名乎莉,當(dāng)socket被關(guān)閉之后會自動刪除這個抽象名
- 無需為socket創(chuàng)建一個文件系統(tǒng)路徑名了奸笤,這對于chroot緩解以及在不具備文件系統(tǒng)上的寫權(quán)限時比較有用的监右。
SOCKET:TCP/IP 網(wǎng)絡(luò)基礎(chǔ)
網(wǎng)絡(luò)協(xié)議和層
數(shù)據(jù)鏈路層
要傳輸數(shù)據(jù)健盒,數(shù)據(jù)鏈路層需要將網(wǎng)絡(luò)層傳遞過來的數(shù)據(jù)報封裝進(jìn)被稱為幀的一個一個單元味榛。最大傳輸單元MTC是改層所能傳輸?shù)膸笮〉纳舷蕖?/p>
網(wǎng)絡(luò)層 IP
網(wǎng)絡(luò)層任務(wù):
- 將數(shù)據(jù)分解成足夠小的片段以便數(shù)據(jù)鏈路層進(jìn)行傳輸
- 在因特網(wǎng)上路由數(shù)據(jù)
- 為傳輸層提供服務(wù)
網(wǎng)絡(luò)層的協(xié)議是IP予跌,IPv4使用32位地址來標(biāo)識子網(wǎng)和主機(jī)券册,IPv6則使用了128位的地址。
一個裸socket(SOCK_RAW),允許程序直接與IP層進(jìn)行通信航邢,但大多數(shù)都會基于一種傳輸層協(xié)議之上的socket骄蝇。
IP傳輸數(shù)據(jù)報
IP以數(shù)據(jù)報(包)的形式來傳輸數(shù)據(jù)九火。在兩個主機(jī)之間發(fā)送的每一個數(shù)據(jù)報都是在網(wǎng)絡(luò)上獨(dú)立傳輸?shù)模鼈兘?jīng)過的路徑可能會不同勒极。一個IP數(shù)據(jù)報包含一個頭虑鼎,其大小范圍為20字節(jié)到60字節(jié)。包含目標(biāo)主機(jī)的地址絮短,源地址昨忆。
一個IP實(shí)現(xiàn)可能會給它所支持的數(shù)據(jù)報的大小設(shè)定一個上限。所有IP實(shí)現(xiàn)都必須做到數(shù)據(jù)報的大小上限至少與規(guī)定的IP最小重組緩沖區(qū)大小一樣大限府。IPv4限制值是576字節(jié)胁勺,IPv6是1500字節(jié)独旷。
IP是無連接和不可靠的
IP是一種無連接協(xié)議,并沒有在相互連接的兩個主機(jī)之間提供一個虛擬電路案疲。
IP是一種不可靠的協(xié)議:盡最大可能將數(shù)據(jù)報從發(fā)送者傳輸給接收者褐啡,但并不保證包到達(dá)的順序與它們被傳輸?shù)捻樞蛞恢卤畈膊槐WC是否重復(fù)许昨,甚至到達(dá)。IP也美譽(yù)錯誤恢復(fù)莉恼∷倌牵可靠性是通過使用TCP來保證的琅坡。
IPv4為IP頭提供了一個校驗(yàn)和,這樣能夠檢測出頭中的錯誤售躁,但并沒有為包中所傳輸?shù)臄?shù)據(jù)提供任何錯誤檢測機(jī)制。IPv6并沒有為IP頭提供校驗(yàn)和回窘,它依賴高層協(xié)議來完成錯誤檢測和可靠性市袖。
IP數(shù)據(jù)報的重復(fù)使可能發(fā)生的苍碟,數(shù)據(jù)鏈路層采用一些技術(shù)確保可靠性以及IP數(shù)據(jù)報可能會以隧道形式穿越采用了重傳機(jī)制舷丹。
IP對數(shù)據(jù)報進(jìn)行分段
IP會將數(shù)據(jù)報分段成一個個大小合適的傳輸單元颜凯,這些分段在到達(dá)最終目的之后會被重組成原始的數(shù)據(jù)報(每個IP分段本身就包含一個偏移量)