一碉钠、Socket技術(shù)詳解

Socket原理

1纲缓、什么是Socket

在計算機通信領(lǐng)域,socket 被翻譯為“套接字”喊废,它是計算機之間進行通信一種約定或一種方式祝高。通過 socket 這種約定,一臺計算機可以接收其他計算機的數(shù)據(jù)污筷,也可以向其他計算機發(fā)送數(shù)據(jù)
  socket起源于Unix工闺,而Unix/Linux基本哲學(xué)之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關(guān)閉close”模式來操作。
  我的理解就是Socket就是該模式的一個實現(xiàn):即socket是一種特殊的文件陆蟆,一些socket函數(shù)就是對其進行的操作(讀/寫IO雷厂、打開、關(guān)閉)叠殷。
  Socket()函數(shù)返回一個整型的Socket描述符改鲫,隨后的連接建立、數(shù)據(jù)傳輸?shù)炔僮鞫际峭ㄟ^該Socket實現(xiàn)的林束。

2钩杰、網(wǎng)絡(luò)中進程如何通信

既然Socket主要是用來解決網(wǎng)絡(luò)通信的,那么我們就來理解網(wǎng)絡(luò)中進程是如何通信的诊县。

2.1、本地進程間通信

a措左、消息傳遞(管道依痊、消息隊列、FIFO)
  b怎披、同步(互斥量胸嘁、條件變量、讀寫鎖凉逛、文件和寫記錄鎖性宏、信號量)
  c、共享內(nèi)存(匿名的和具名的状飞,eg:channel)
  d毫胜、遠程過程調(diào)用(RPC)

2.2、網(wǎng)絡(luò)中進程如何通信

我們要理解網(wǎng)絡(luò)中進程如何通信诬辈,得解決兩個問題:
 〗褪埂a、我們要如何標識一臺主機焙糟,即怎樣確定我們將要通信的進程是在那一臺主機上運行口渔。
  b穿撮、我們要如何標識唯一進程缺脉,本地通過pid標識,網(wǎng)絡(luò)中應(yīng)該怎樣標識悦穿?
解決辦法:
 」ダ瘛a、TCP/IP協(xié)議族已經(jīng)幫我們解決了這個問題咧党,網(wǎng)絡(luò)層的“ip地址”可以唯一標識網(wǎng)絡(luò)中的主機
 ∶鼗住b、傳輸層的“協(xié)議+端口”可以唯一標識主機中的應(yīng)用程序(進程),因此深员,我們利用三元組(ip地址负蠕,協(xié)議,端口)就可以標識網(wǎng)絡(luò)的進程了倦畅,網(wǎng)絡(luò)中的進程通信就可以利用這個標志與其它進程進行交互

3遮糖、Socket怎么通信

現(xiàn)在,我們知道了網(wǎng)絡(luò)中進程間如何通信叠赐,即利用三元組【ip地址欲账,協(xié)議,端口】可以進行網(wǎng)絡(luò)間通信了芭概,那我們應(yīng)該怎么實現(xiàn)了赛不,因此,我們socket應(yīng)運而生罢洲,它就是利用三元組解決網(wǎng)絡(luò)通信的一個中間件工具踢故,就目前而言,幾乎所有的應(yīng)用程序都是采用socket惹苗,如UNIX BSD的套接字(socket)和UNIX System V的TLI(已經(jīng)被淘汰)殿较。
Socket通信的數(shù)據(jù)傳輸方式,常用的有兩種:
 ∽亍a淋纲、SOCK_STREAM:表示面向連接的數(shù)據(jù)傳輸方式。數(shù)據(jù)可以準確無誤地到達另一臺計算機院究,如果損壞或丟失洽瞬,可以重新發(fā)送,但效率相對較慢业汰。常見的 http 協(xié)議就使用 SOCK_STREAM 傳輸數(shù)據(jù)片任,因為要確保數(shù)據(jù)的正確性,否則網(wǎng)頁不能正常解析蔬胯。
 《怨b、SOCK_DGRAM:表示無連接的數(shù)據(jù)傳輸方式氛濒。計算機只管傳輸數(shù)據(jù)产场,不作數(shù)據(jù)校驗,如果數(shù)據(jù)在傳輸中損壞舞竿,或者沒有到達另一臺計算機京景,是沒有辦法補救的。也就是說骗奖,數(shù)據(jù)錯了就錯了确徙,無法重傳醒串。因為 SOCK_DGRAM 所做的校驗工作少,所以效率比 SOCK_STREAM 高鄙皇。
  例如:QQ 視頻聊天和語音聊天就使用 SOCK_DGRAM 傳輸數(shù)據(jù)芜赌,因為首先要保證通信的效率,盡量減小延遲伴逸,而數(shù)據(jù)的正確性是次要的缠沈,即使丟失很小的一部分數(shù)據(jù),視頻和音頻也可以正常解析错蝴,最多出現(xiàn)噪點或雜音洲愤,不會對通信質(zhì)量有實質(zhì)的影響

4、TCP/IP協(xié)議

4.1顷锰、概念

TCP/IP【TCP(傳輸控制協(xié)議)和IP(網(wǎng)際協(xié)議)】提供點對點的鏈接機制柬赐,將數(shù)據(jù)應(yīng)該如何封裝、定址官紫、傳輸躺率、路由以及在目的地如何接收,都加以標準化万矾。它將軟件通信過程抽象化為四個抽象層,采取協(xié)議堆棧的方式慎框,分別實現(xiàn)出不同通信協(xié)議良狈。協(xié)議族下的各種協(xié)議,依其功能不同笨枯,被分別歸屬到這四個層次結(jié)構(gòu)之中薪丁,常被視為是簡化的七層OSI模型。

它們之間好比送信的線路和驛站的作用馅精,比如要建議送信驛站严嗜,必須得了解送信的各個細節(jié)。

TCP(Transmission Control Protocol洲敢,傳輸控制協(xié)議)是一種面向連接的漫玄、可靠的、基于字節(jié)流的通信協(xié)議压彭,數(shù)據(jù)在傳輸前要建立連接睦优,傳輸完畢后還要斷開連接,客戶端在收發(fā)數(shù)據(jù)前要使用 connect() 函數(shù)和服務(wù)器建立連接壮不。建立連接的目的是保證IP地址汗盘、端口、物理鏈路等正確無誤询一,為數(shù)據(jù)的傳輸開辟通道隐孽。
TCP建立連接時要傳輸三個數(shù)據(jù)包癌椿,俗稱三次握手(Three-way Handshaking)×庹螅可以形象的比喻為下面的對話:

[Shake 1] 套接字A:“你好踢俄,套接字B,我這里有數(shù)據(jù)要傳送給你送粱,建立連接吧褪贵。”
[Shake 2] 套接字B:“好的抗俄,我這邊已準備就緒脆丁。”
[Shake 3] 套接字A:“謝謝你受理我的請求动雹。
4.2槽卫、TCP的粘包問題以及數(shù)據(jù)的無邊界性: https://blog.csdn.net/m0_37947204/article/details/80490512
4.4、TCP數(shù)據(jù)報結(jié)構(gòu):
20180529001000428.jpeg

帶陰影的幾個字段需要重點說明一下:
  (1) 序號:Seq(Sequence Number)序號占32位胰蝠,用來標識從計算機A發(fā)送到計算機B的數(shù)據(jù)包的序號歼培,計算機發(fā)送數(shù)據(jù)時對此進行標記。
  (2) 確認號:Ack(Acknowledge Number)確認號占32位茸塞,客戶端和服務(wù)器端都可以發(fā)送躲庄,Ack = Seq + 1。
  (3) 標志位:每個標志位占用1Bit钾虐,共有6個噪窘,分別為 URG、ACK效扫、PSH倔监、RST、SYN菌仁、FIN浩习,具體含義如下:

(1)URG:緊急指針(urgent pointer)有效。
(2)ACK:確認序號有效济丘。
(3)PSH:接收方應(yīng)該盡快將這個報文交給應(yīng)用層谱秽。
(4)RST:重置連接。
(5)SYN:建立一個新連接摹迷。
(6)FIN:斷開一個連接弯院。
4.5、連接的建立(三次握手):

使用 connect() 建立連接時泪掀,客戶端和服務(wù)器端會相互發(fā)送三個數(shù)據(jù)包听绳,請看下圖:

  
20180529001324885.jpeg

客戶端調(diào)用 socket() 函數(shù)創(chuàng)建套接字后,因為沒有建立連接异赫,所以套接字處于CLOSED狀態(tài)椅挣;服務(wù)器端調(diào)用 listen() 函數(shù)后头岔,套接字進入LISTEN狀態(tài),開始監(jiān)聽客戶端請求
這時客戶端發(fā)起請求:
  1) 當(dāng)客戶端調(diào)用 connect() 函數(shù)后鼠证,TCP協(xié)議會組建一個數(shù)據(jù)包峡竣,并設(shè)置 SYN 標志位,表示該數(shù)據(jù)包是用來建立同步連接的量九。同時生成一個隨機數(shù)字 1000适掰,填充“序號(Seq)”字段,表示該數(shù)據(jù)包的序號荠列。完成這些工作类浪,開始向服務(wù)器端發(fā)送數(shù)據(jù)包,客戶端就進入了SYN-SEND狀態(tài)肌似。
  2) 服務(wù)器端收到數(shù)據(jù)包费就,檢測到已經(jīng)設(shè)置了 SYN 標志位,就知道這是客戶端發(fā)來的建立連接的“請求包”川队。服務(wù)器端也會組建一個數(shù)據(jù)包力细,并設(shè)置 SYN 和 ACK 標志位,SYN 表示該數(shù)據(jù)包用來建立連接固额,ACK 用來確認收到了剛才客戶端發(fā)送的數(shù)據(jù)包
  服務(wù)器生成一個隨機數(shù) 2000眠蚂,填充“序號(Seq)”字段。2000 和客戶端數(shù)據(jù)包沒有關(guān)系斗躏。
  服務(wù)器將客戶端數(shù)據(jù)包序號(1000)加1逝慧,得到1001,并用這個數(shù)字填充“確認號(Ack)”字段瑟捣。
  服務(wù)器將數(shù)據(jù)包發(fā)出,進入SYN-RECV狀態(tài)
  3) 客戶端收到數(shù)據(jù)包栅干,檢測到已經(jīng)設(shè)置了 SYN 和 ACK 標志位迈套,就知道這是服務(wù)器發(fā)來的“確認包”〖盍郏客戶端會檢測“確認號(Ack)”字段桑李,看它的值是否為 1000+1,如果是就說明連接建立成功窿给。
  接下來贵白,客戶端會繼續(xù)組建數(shù)據(jù)包,并設(shè)置 ACK 標志位崩泡,表示客戶端正確接收了服務(wù)器發(fā)來的“確認包”禁荒。同時,將剛才服務(wù)器發(fā)來的數(shù)據(jù)包序號(2000)加1角撞,得到 2001呛伴,并用這個數(shù)字來填充“確認號(Ack)”字段勃痴。
  客戶端將數(shù)據(jù)包發(fā)出,進入ESTABLISED狀態(tài)热康,表示連接已經(jīng)成功建立沛申。
  4) 服務(wù)器端收到數(shù)據(jù)包,檢測到已經(jīng)設(shè)置了 ACK 標志位姐军,就知道這是客戶端發(fā)來的“確認包”铁材。服務(wù)器會檢測“確認號(Ack)”字段,看它的值是否為 2000+1奕锌,如果是就說明連接建立成功著觉,服務(wù)器進入ESTABLISED狀態(tài)。
  至此歇攻,客戶端和服務(wù)器都進入了ESTABLISED狀態(tài)固惯,連接建立成功,接下來就可以收發(fā)數(shù)據(jù)了缴守。

4.6葬毫、TCP四次握手斷開連接

建立連接非常重要,它是數(shù)據(jù)正確傳輸?shù)那疤崧潘耄粩嚅_連接同樣重要贴捡,它讓計算機釋放不再使用的資源。如果連接不能正常斷開村砂,不僅會造成數(shù)據(jù)傳輸錯誤烂斋,還會導(dǎo)致套接字不能關(guān)閉,持續(xù)占用資源础废,如果并發(fā)量高汛骂,服務(wù)器壓力堪憂。
斷開連接需要四次握手评腺,可以形象的比喻為下面的對話:

[Shake 1] 套接字A:“任務(wù)處理完畢帘瞭,我希望斷開連接≥锛ィ”
[Shake 2] 套接字B:“哦蝶念,是嗎?請稍等芋绸,我準備一下媒殉。”
等待片刻后……
[Shake 3] 套接字B:“我準備好了摔敛,可以斷開連接了廷蓉。”
[Shake 4] 套接字A:“好的马昙,謝謝合作苦酱∈勖玻”

下圖演示了客戶端主動斷開連接的場景:


20180529001837204.jpeg

建立連接后,客戶端和服務(wù)器都處于ESTABLISED狀態(tài)疫萤。這時颂跨,客戶端發(fā)起斷開連接的請求:

  1. 客戶端調(diào)用 close() 函數(shù)后,向服務(wù)器發(fā)送 FIN 數(shù)據(jù)包扯饶,進入FIN_WAIT_1狀態(tài)恒削。FIN 是 Finish 的縮寫,表示完成任務(wù)需要斷開連接尾序。
  2. 服務(wù)器收到數(shù)據(jù)包后钓丰,檢測到設(shè)置了 FIN 標志位,知道要斷開連接每币,于是向客戶端發(fā)送“確認包”携丁,進入CLOSE_WAIT狀態(tài)。
    注意:服務(wù)器收到請求后并不是立即斷開連接兰怠,而是先向客戶端發(fā)送“確認包”梦鉴,告訴它我知道了,我需要準備一下才能斷開連接揭保。
  3. 客戶端收到“確認包”后進入FIN_WAIT_2狀態(tài)肥橙,等待服務(wù)器準備完畢后再次發(fā)送數(shù)據(jù)包。
  4. 等待片刻后秸侣,服務(wù)器準備完畢存筏,可以斷開連接,于是再主動向客戶端發(fā)送 FIN 包味榛,告訴它我準備好了椭坚,斷開連接吧。然后進入LAST_ACK狀態(tài)搏色。
  5. 客戶端收到服務(wù)器的 FIN 包后善茎,再向服務(wù)器發(fā)送 ACK 包,告訴它你斷開連接吧继榆。然后進入TIME_WAIT狀態(tài)巾表。
  6. 服務(wù)器收到客戶端的 ACK 包后汁掠,就斷開連接略吨,關(guān)閉套接字,進入CLOSED狀態(tài)考阱。
4.7翠忠、關(guān)于 TIME_WAIT 狀態(tài)的說明

客戶端最后一次發(fā)送 ACK包后進入 TIME_WAIT 狀態(tài),而不是直接進入 CLOSED 狀態(tài)關(guān)閉連接乞榨,這是為什么呢秽之?

TCP 是面向連接的傳輸方式当娱,必須保證數(shù)據(jù)能夠正確到達目標機器,不能丟失或出錯考榨,而網(wǎng)絡(luò)是不穩(wěn)定的跨细,隨時可能會毀壞數(shù)據(jù),所以機器A每次向機器B發(fā)送數(shù)據(jù)包后河质,都要求機器B”確認“冀惭,回傳ACK包,告訴機器A我收到了掀鹅,這樣機器A才能知道數(shù)據(jù)傳送成功了散休。如果機器B沒有回傳ACK包,機器A會重新發(fā)送乐尊,直到機器B回傳ACK包戚丸。

客戶端最后一次向服務(wù)器回傳ACK包時,有可能會因為網(wǎng)絡(luò)問題導(dǎo)致服務(wù)器收不到扔嵌,服務(wù)器會再次發(fā)送 FIN 包限府,如果這時客戶端完全關(guān)閉了連接,那么服務(wù)器無論如何也收不到ACK包了对人,所以客戶端需要等待片刻谣殊、確認對方收到ACK包后才能進入CLOSED狀態(tài)。那么牺弄,要等待多久呢姻几?

數(shù)據(jù)包在網(wǎng)絡(luò)中是有生存時間的,超過這個時間還未到達目標主機就會被丟棄势告,并通知源主機蛇捌。這稱為報文最大生存時間(MSL,Maximum Segment Lifetime)咱台。TIME_WAIT 要等待 2MSL 才會進入 CLOSED 狀態(tài)络拌。ACK 包到達服務(wù)器需要 MSL 時間,服務(wù)器重傳 FIN 包也需要 MSL 時間回溺,2MSL 是數(shù)據(jù)包往返的最大時間春贸,如果 2MSL 后還未收到服務(wù)器重傳的 FIN 包,就說明服務(wù)器已經(jīng)收到了 ACK 包

4.8.優(yōu)雅的斷開連接–shutdown()

close()/closesocket()和shutdown()的區(qū)別
確切地說遗遵,close() / closesocket() 用來關(guān)閉套接字萍恕,將套接字描述符(或句柄)從內(nèi)存清除,之后再也不能使用該套接字车要,與C語言中的 fclose() 類似允粤。應(yīng)用程序關(guān)閉套接字后,與該套接字相關(guān)的連接和緩存也失去了意義,TCP協(xié)議會自動觸發(fā)關(guān)閉連接的操作类垫。

shutdown() 用來關(guān)閉連接司光,而不是套接字,不管調(diào)用多少次 shutdown()悉患,套接字依然存在残家,直到調(diào)用 close() / closesocket() 將套接字從內(nèi)存清除。
調(diào)用 close()/closesocket() 關(guān)閉套接字時售躁,或調(diào)用 shutdown() 關(guān)閉輸出流時跪削,都會向?qū)Ψ桨l(fā)送 FIN 包。FIN 包表示數(shù)據(jù)傳輸完畢迂求,計算機收到 FIN 包就知道不會再有數(shù)據(jù)傳送過來了碾盐。

默認情況下,close()/closesocket() 會立即向網(wǎng)絡(luò)中發(fā)送FIN包揩局,不管輸出緩沖區(qū)中是否還有數(shù)據(jù)毫玖,而shutdown() 會等輸出緩沖區(qū)中的數(shù)據(jù)傳輸完畢再發(fā)送FIN包。也就意味著凌盯,調(diào)用 close()/closesocket() 將丟失輸出緩沖區(qū)中的數(shù)據(jù)付枫,而調(diào)用 shutdown() 不會

5、OSI模型

TCP/IP對OSI的網(wǎng)絡(luò)模型層進行了劃分如下:


20150615140039701.jpeg

TCP/IP協(xié)議參考模型把所有的TCP/IP系列協(xié)議歸類到四個抽象層中
  應(yīng)用層:TFTP驰怎,HTTP阐滩,SNMP,F(xiàn)TP县忌,SMTP掂榔,DNS,Telnet 等等
  傳輸層:TCP症杏,UDP
  網(wǎng)絡(luò)層:IP装获,ICMP,OSPF厉颤,EIGRP穴豫,IGMP
  數(shù)據(jù)鏈路層:SLIP,CSLIP逼友,PPP精肃,MTU
  每一抽象層建立在低一層提供的服務(wù)上,并且為高一層提供服務(wù)帜乞,看起來大概是這樣子的

  
20150615140707753.png
20150615141705040.png

6司抱、Socket常用函數(shù)接口及其原理

圖解socket函數(shù):


20150615150446559.png
20150615150618996.jpeg
6.1、使用socket()函數(shù)創(chuàng)建套接字
int socket(int af, int type, int protocol);
  1. af 為地址族(Address Family)挖函,也就是 IP 地址類型状植,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的簡寫怨喘,INET是“Inetnet”的簡寫津畸。AF_INET 表示 IPv4 地址,例如 127.0.0.1必怜;AF_INET6 表示 IPv6 地址肉拓,例如 1030::C9B4:FF12:48AA:1A2B。
    大家需要記住127.0.0.1梳庆,它是一個特殊IP地址暖途,表示本機地址,后面的教程會經(jīng)常用到膏执。
  2. type 為數(shù)據(jù)傳輸方式驻售,常用的有 SOCK_STREAM 和 SOCK_DGRAM
  3. protocol 表示傳輸協(xié)議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP更米,分別表示 TCP 傳輸協(xié)議和 UDP 傳輸協(xié)議
6.2欺栗、使用bind()和connect()函數(shù)

socket() 函數(shù)用來創(chuàng)建套接字,確定套接字的各種屬性征峦,然后服務(wù)器端要用 bind() 函數(shù)將套接字與特定的IP地址和端口綁定起來迟几,只有這樣,流經(jīng)該IP地址和端口的數(shù)據(jù)才能交給套接字處理栏笆;而客戶端要用 connect() 函數(shù)建立連接

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  

sock 為 socket 文件描述符类腮,addr 為 sockaddr 結(jié)構(gòu)體變量的指針,addrlen 為 addr 變量的大小蛉加,可由 sizeof() 計算得出
下面的代碼蚜枢,將創(chuàng)建的套接字與IP地址 127.0.0.1、端口 1234 綁定:

//創(chuàng)建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//創(chuàng)建sockaddr_in結(jié)構(gòu)體變量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));  //每個字節(jié)都用0填充
serv_addr.sin_family = AF_INET;  //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
serv_addr.sin_port = htons(1234);  //端口
//將套接字和IP针饥、端口綁定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

connect() 函數(shù)用來建立連接祟偷,它的原型為:

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); 
6.3、使用listen()和accept()函數(shù)

于服務(wù)器端程序打厘,使用 bind() 綁定套接字后修肠,還需要使用 listen() 函數(shù)讓套接字進入被動監(jiān)聽狀態(tài),再調(diào)用 accept() 函數(shù)户盯,就可以隨時響應(yīng)客戶端的請求了嵌施。
通過** listen() 函數(shù)**可以讓套接字進入被動監(jiān)聽狀態(tài),它的原型為:

int listen(int sock, int backlog); 

sock 為需要進入監(jiān)聽狀態(tài)的套接字莽鸭,backlog 為請求隊列的最大長度吗伤。
所謂被動監(jiān)聽,是指當(dāng)沒有客戶端請求時硫眨,套接字處于“睡眠”狀態(tài)足淆,只有當(dāng)接收到客戶端請求時,套接字才會被“喚醒”來響應(yīng)請求。

請求隊列
當(dāng)套接字正在處理客戶端請求時巧号,如果有新的請求進來族奢,套接字是沒法處理的,只能把它放進緩沖區(qū)丹鸿,待當(dāng)前請求處理完畢后越走,再從緩沖區(qū)中讀取出來處理。如果不斷有新的請求進來靠欢,它們就按照先后順序在緩沖區(qū)中排隊廊敌,直到緩沖區(qū)滿。這個緩沖區(qū)门怪,就稱為請求隊列(Request Queue)骡澈。

緩沖區(qū)的長度(能存放多少個客戶端請求)可以通過 listen() 函數(shù)的 backlog 參數(shù)指定,但究竟為多少并沒有什么標準掷空,可以根據(jù)你的需求來定秧廉,并發(fā)量小的話可以是10或者20。

如果將 backlog 的值設(shè)置為 SOMAXCONN拣帽,就由系統(tǒng)來決定請求隊列長度疼电,這個值一般比較大,可能是幾百减拭,或者更多蔽豺。

當(dāng)請求隊列滿時,就不再接收新的請求,對于 Linux,客戶端會收到 ECONNREFUSED 錯誤

注意:listen() 只是讓套接字處于監(jiān)聽狀態(tài)聚假,并沒有接收請求晚伙。接收請求需要使用 accept() 函數(shù)欧啤。

當(dāng)套接字處于監(jiān)聽狀態(tài)時,可以通過 accept() 函數(shù)來接收客戶端請求。它的原型為:

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); 

它的參數(shù)與 listen() 和 connect() 是相同的:sock 為服務(wù)器端套接字,addr 為 sockaddr_in 結(jié)構(gòu)體變量拾因,addrlen 為參數(shù) addr 的長度,可由 sizeof() 求得旷余。

accept() 返回一個新的套接字來和客戶端通信绢记,addr 保存了客戶端的IP地址和端口號,而 sock 是服務(wù)器端的套接字正卧,大家注意區(qū)分蠢熄。后面和客戶端通信時,要使用這個新生成的套接字炉旷,而不是原來服務(wù)器端的套接字签孔。

最后需要說明的是:listen() 只是讓套接字進入監(jiān)聽狀態(tài)叉讥,并沒有真正接收客戶端請求,listen() 后面的代碼會繼續(xù)執(zhí)行饥追,直到遇到 accept()图仓。accept() 會阻塞程序執(zhí)行(后面代碼不能被執(zhí)行),直到有新的請求到來判耕。

6.4、socket數(shù)據(jù)的接收和發(fā)送

Linux下數(shù)據(jù)的接收和發(fā)送
Linux 不區(qū)分套接字文件和普通文件翘骂,使用 write() 可以向套接字中寫入數(shù)據(jù)壁熄,使用 read() 可以從套接字中讀取數(shù)據(jù)。

前面我們說過碳竟,兩臺計算機之間的通信相當(dāng)于兩個套接字之間的通信草丧,在服務(wù)器端用 write() 向套接字寫入數(shù)據(jù),客戶端就能收到莹桅,然后再使用 read() 從套接字中讀取出來昌执,就完成了一次通信。
write() 的原型為:

ssize_t write(int fd, const void *buf, size_t nbytes);

fd 為要寫入的文件的描述符诈泼,buf 為要寫入的數(shù)據(jù)的緩沖區(qū)地址懂拾,nbytes 為要寫入的數(shù)據(jù)的字節(jié)數(shù)。
write() 函數(shù)會將緩沖區(qū) buf 中的 nbytes 個字節(jié)寫入文件 fd铐达,成功則返回寫入的字節(jié)數(shù)岖赋,失敗則返回 -1。
read() 的原型為:

ssize_t read(int fd, void *buf, size_t nbytes);

fd 為要讀取的文件的描述符瓮孙,buf 為要接收數(shù)據(jù)的緩沖區(qū)地址唐断,nbytes 為要讀取的數(shù)據(jù)的字節(jié)數(shù)。

read() 函數(shù)會從 fd 文件中讀取 nbytes 個字節(jié)并保存到緩沖區(qū) buf杭抠,成功則返回讀取到的字節(jié)數(shù)(但遇到文件結(jié)尾則返回0)脸甘,失敗則返回 -1。

6.5偏灿、socket緩沖區(qū)以及阻塞模式

socket緩沖區(qū)
每個 socket 被創(chuàng)建后丹诀,都會分配兩個緩沖區(qū),輸入緩沖區(qū)和輸出緩沖區(qū)翁垂。

write()/send() 并不立即向網(wǎng)絡(luò)中傳輸數(shù)據(jù)忿墅,而是先將數(shù)據(jù)寫入緩沖區(qū)中,再由TCP協(xié)議將數(shù)據(jù)從緩沖區(qū)發(fā)送到目標機器沮峡。一旦將數(shù)據(jù)寫入到緩沖區(qū)疚脐,函數(shù)就可以成功返回,不管它們有沒有到達目標機器邢疙,也不管它們何時被發(fā)送到網(wǎng)絡(luò)棍弄,這些都是TCP協(xié)議負責(zé)的事情望薄。

TCP協(xié)議獨立于 write()/send() 函數(shù),數(shù)據(jù)有可能剛被寫入緩沖區(qū)就發(fā)送到網(wǎng)絡(luò)呼畸,也可能在緩沖區(qū)中不斷積壓痕支,多次寫入的數(shù)據(jù)被一次性發(fā)送到網(wǎng)絡(luò),這取決于當(dāng)時的網(wǎng)絡(luò)情況蛮原、當(dāng)前線程是否空閑等諸多因素卧须,不由程序員控制。

read()/recv() 函數(shù)也是如此儒陨,也從輸入緩沖區(qū)中讀取數(shù)據(jù)花嘶,而不是直接從網(wǎng)絡(luò)中讀取

20180528234331238.jpeg

這些I/O緩沖區(qū)特性可整理如下:

(1)I/O緩沖區(qū)在每個TCP套接字中單獨存在;
(2)I/O緩沖區(qū)在創(chuàng)建套接字時自動生成蹦漠;
(3)即使關(guān)閉套接字也會繼續(xù)傳送輸出緩沖區(qū)中遺留的數(shù)據(jù)椭员;
(4)關(guān)閉套接字將丟失輸入緩沖區(qū)中的數(shù)據(jù)。

輸入輸出緩沖區(qū)的默認大小一般都是 8K笛园,可以通過 getsockopt() 函數(shù)獲劝鳌:

unsigned optVal;
int optLen = sizeof(int);
getsockopt(servSock, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen);
printf("Buffer length: %d\n", optVal);

阻塞模式
對于TCP套接字(默認情況下),當(dāng)使用 write()/send() 發(fā)送數(shù)據(jù)時:

1) 首先會檢查緩沖區(qū)研铆,如果緩沖區(qū)的可用空間長度小于要發(fā)送的數(shù)據(jù)埋同,那么 write()/send() 會被阻塞(暫停執(zhí)行),直到緩沖區(qū)中的數(shù)據(jù)被發(fā)送到目標機器棵红,騰出足夠的空間莺禁,才喚醒 write()/send() 函數(shù)繼續(xù)寫入數(shù)據(jù)。
2) 如果TCP協(xié)議正在向網(wǎng)絡(luò)發(fā)送數(shù)據(jù)窄赋,那么輸出緩沖區(qū)會被鎖定哟冬,不允許寫入,write()/send() 也會被阻塞忆绰,直到數(shù)據(jù)發(fā)送完畢緩沖區(qū)解鎖浩峡,write()/send() 才會被喚醒。
3) 如果要寫入的數(shù)據(jù)大于緩沖區(qū)的最大長度错敢,那么將分批寫入翰灾。
4) 直到所有數(shù)據(jù)被寫入緩沖區(qū) write()/send() 才能返回。

當(dāng)使用 read()/recv() 讀取數(shù)據(jù)時:

1) 首先會檢查緩沖區(qū)稚茅,如果緩沖區(qū)中有數(shù)據(jù)纸淮,那么就讀取,否則函數(shù)會被阻塞亚享,直到網(wǎng)絡(luò)上有數(shù)據(jù)到來咽块。
2) 如果要讀取的數(shù)據(jù)長度小于緩沖區(qū)中的數(shù)據(jù)長度,那么就不能一次性將緩沖區(qū)中的所有數(shù)據(jù)讀出欺税,剩余數(shù)據(jù)將不斷積壓侈沪,直到有 read()/recv() 函數(shù)再次讀取揭璃。
3) 直到讀取到數(shù)據(jù)后 read()/recv() 函數(shù)才會返回,否則就一直被阻塞亭罪。
這就是TCP套接字的阻塞模式瘦馍。所謂阻塞,就是上一步動作沒有完成应役,下一步動作將暫停情组,直到上一步動作完成后才能繼續(xù),以保持同步性箩祥。

TCP套接字默認情況下是阻塞模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末院崇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子滥比,更是在濱河造成了極大的恐慌亚脆,老刑警劉巖做院,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盲泛,死亡現(xiàn)場離奇詭異,居然都是意外死亡键耕,警方通過查閱死者的電腦和手機寺滚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屈雄,“玉大人村视,你說我怎么就攤上這事【颇蹋” “怎么了蚁孔?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惋嚎。 經(jīng)常有香客問我杠氢,道長,這世上最難降的妖魔是什么另伍? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任鼻百,我火速辦了婚禮,結(jié)果婚禮上摆尝,老公的妹妹穿的比我還像新娘温艇。我一直安慰自己,他們只是感情好堕汞,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布勺爱。 她就那樣靜靜地躺著,像睡著了一般讯检。 火紅的嫁衣襯著肌膚如雪邻寿。 梳的紋絲不亂的頭發(fā)上蝎土,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音绣否,去河邊找鬼誊涯。 笑死,一個胖子當(dāng)著我的面吹牛蒜撮,可吹牛的內(nèi)容都是我干的暴构。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼段磨,長吁一口氣:“原來是場噩夢啊……” “哼取逾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苹支,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤砾隅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后债蜜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晴埂,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年寻定,在試婚紗的時候發(fā)現(xiàn)自己被綠了儒洛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡狼速,死狀恐怖琅锻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情向胡,我是刑警寧澤恼蓬,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站僵芹,受9級特大地震影響处硬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜淮捆,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一郁油、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧攀痊,春花似錦桐腌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至棘街,卻和暖如春蟆盐,著一層夾襖步出監(jiān)牢的瞬間承边,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工石挂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留博助,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓痹愚,卻偏偏與公主長得像富岳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拯腮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容