Socket原理
1、什么是Socket
在計算機通信領(lǐng)域,socket 被翻譯為“套接字”,它是計算機之間進行通信的一種約定或一種方式。通過 socket 這種約定,一臺計算機可以接收其他計算機的數(shù)據(jù),也可以向其他計算機發(fā)送數(shù)據(jù)
socket起源于Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開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)絡中進程如何通信
既然Socket主要是用來解決網(wǎng)絡通信的,那么我們就來理解網(wǎng)絡中進程是如何通信的。
2.1、本地進程間通信
a、消息傳遞(管道、消息隊列、FIFO)
b、同步(互斥量、條件變量、讀寫鎖、文件和寫記錄鎖、信號量)
c、共享內(nèi)存(匿名的和具名的,eg:channel)
d、遠程過程調(diào)用(RPC)
2.2、網(wǎng)絡中進程如何通信
我們要理解網(wǎng)絡中進程如何通信,得解決兩個問題:
a、我們要如何標識一臺主機,即怎樣確定我們將要通信的進程是在那一臺主機上運行。
?。?、我們要如何標識唯一進程,本地通過pid標識,網(wǎng)絡中應該怎樣標識?
解決辦法:
a、TCP/IP協(xié)議族已經(jīng)幫我們解決了這個問題,網(wǎng)絡層的“ip地址”可以唯一標識網(wǎng)絡中的主機
?。?、傳輸層的“協(xié)議+端口”可以唯一標識主機中的應用程序(進程),因此,我們利用三元組(ip地址,協(xié)議,端口)就可以標識網(wǎng)絡的進程了,網(wǎng)絡中的進程通信就可以利用這個標志與其它進程進行交互
3、Socket怎么通信
現(xiàn)在,我們知道了網(wǎng)絡中進程間如何通信,即利用三元組【ip地址,協(xié)議,端口】可以進行網(wǎng)絡間通信了,那我們應該怎么實現(xiàn)了,因此,我們socket應運而生,它就是利用三元組解決網(wǎng)絡通信的一個中間件工具,就目前而言,幾乎所有的應用程序都是采用socket,如UNIX BSD的套接字(socket)和UNIX System V的TLI(已經(jīng)被淘汰)。
Socket通信的數(shù)據(jù)傳輸方式,常用的有兩種:
?。?、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ù)應該如何封裝、定址、傳輸、路由以及在目的地如何接收,都加以標準化。它將軟件通信過程抽象化為四個抽象層,采取協(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ù)和服務器建立連接。建立連接的目的是保證IP地址、端口、物理鏈路等正確無誤,為數(shù)據(jù)的傳輸開辟通道。
TCP建立連接時要傳輸三個數(shù)據(jù)包,俗稱三次握手(Three-way Handshaking)??梢孕蜗蟮谋扔鳛橄旅娴膶υ挘?/p>
[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):

帶陰影的幾個字段需要重點說明一下:
(1) 序號:Seq(Sequence Number)序號占32位,用來標識從計算機A發(fā)送到計算機B的數(shù)據(jù)包的序號,計算機發(fā)送數(shù)據(jù)時對此進行標記。
(2) 確認號:Ack(Acknowledge Number)確認號占32位,客戶端和服務器端都可以發(fā)送,Ack = Seq + 1。
(3) 標志位:每個標志位占用1Bit,共有6個,分別為 URG、ACK、PSH、RST、SYN、FIN,具體含義如下:
(1)URG:緊急指針(urgent pointer)有效。
(2)ACK:確認序號有效。
(3)PSH:接收方應該盡快將這個報文交給應用層。
(4)RST:重置連接。
(5)SYN:建立一個新連接。
(6)FIN:斷開一個連接。
4.5、連接的建立(三次握手):
使用 connect() 建立連接時,客戶端和服務器端會相互發(fā)送三個數(shù)據(jù)包,請看下圖:

客戶端調(diào)用 socket() 函數(shù)創(chuàng)建套接字后,因為沒有建立連接,所以套接字處于CLOSED狀態(tài);服務器端調(diào)用 listen() 函數(shù)后,套接字進入LISTEN狀態(tài),開始監(jiān)聽客戶端請求
這時客戶端發(fā)起請求:
1) 當客戶端調(diào)用 connect() 函數(shù)后,TCP協(xié)議會組建一個數(shù)據(jù)包,并設置 SYN 標志位,表示該數(shù)據(jù)包是用來建立同步連接的。同時生成一個隨機數(shù)字 1000,填充“序號(Seq)”字段,表示該數(shù)據(jù)包的序號。完成這些工作,開始向服務器端發(fā)送數(shù)據(jù)包,客戶端就進入了SYN-SEND狀態(tài)。
2) 服務器端收到數(shù)據(jù)包,檢測到已經(jīng)設置了 SYN 標志位,就知道這是客戶端發(fā)來的建立連接的“請求包”。服務器端也會組建一個數(shù)據(jù)包,并設置 SYN 和 ACK 標志位,SYN 表示該數(shù)據(jù)包用來建立連接,ACK 用來確認收到了剛才客戶端發(fā)送的數(shù)據(jù)包
服務器生成一個隨機數(shù) 2000,填充“序號(Seq)”字段。2000 和客戶端數(shù)據(jù)包沒有關(guān)系。
服務器將客戶端數(shù)據(jù)包序號(1000)加1,得到1001,并用這個數(shù)字填充“確認號(Ack)”字段。
服務器將數(shù)據(jù)包發(fā)出,進入SYN-RECV狀態(tài)
3) 客戶端收到數(shù)據(jù)包,檢測到已經(jīng)設置了 SYN 和 ACK 標志位,就知道這是服務器發(fā)來的“確認包”??蛻舳藭z測“確認號(Ack)”字段,看它的值是否為 1000+1,如果是就說明連接建立成功。
接下來,客戶端會繼續(xù)組建數(shù)據(jù)包,并設置 ACK 標志位,表示客戶端正確接收了服務器發(fā)來的“確認包”。同時,將剛才服務器發(fā)來的數(shù)據(jù)包序號(2000)加1,得到 2001,并用這個數(shù)字來填充“確認號(Ack)”字段。
客戶端將數(shù)據(jù)包發(fā)出,進入ESTABLISED狀態(tài),表示連接已經(jīng)成功建立。
4) 服務器端收到數(shù)據(jù)包,檢測到已經(jīng)設置了 ACK 標志位,就知道這是客戶端發(fā)來的“確認包”。服務器會檢測“確認號(Ack)”字段,看它的值是否為 2000+1,如果是就說明連接建立成功,服務器進入ESTABLISED狀態(tài)。
至此,客戶端和服務器都進入了ESTABLISED狀態(tài),連接建立成功,接下來就可以收發(fā)數(shù)據(jù)了。
4.6、TCP四次握手斷開連接
建立連接非常重要,它是數(shù)據(jù)正確傳輸?shù)那疤?;斷開連接同樣重要,它讓計算機釋放不再使用的資源。如果連接不能正常斷開,不僅會造成數(shù)據(jù)傳輸錯誤,還會導致套接字不能關(guān)閉,持續(xù)占用資源,如果并發(fā)量高,服務器壓力堪憂。
斷開連接需要四次握手,可以形象的比喻為下面的對話:
[Shake 1] 套接字A:“任務處理完畢,我希望斷開連接。”
[Shake 2] 套接字B:“哦,是嗎?請稍等,我準備一下?!?等待片刻后……
[Shake 3] 套接字B:“我準備好了,可以斷開連接了?!?[Shake 4] 套接字A:“好的,謝謝合作?!?
下圖演示了客戶端主動斷開連接的場景:

建立連接后,客戶端和服務器都處于ESTABLISED狀態(tài)。這時,客戶端發(fā)起斷開連接的請求:
- 客戶端調(diào)用 close() 函數(shù)后,向服務器發(fā)送 FIN 數(shù)據(jù)包,進入FIN_WAIT_1狀態(tài)。FIN 是 Finish 的縮寫,表示完成任務需要斷開連接。
- 服務器收到數(shù)據(jù)包后,檢測到設置了 FIN 標志位,知道要斷開連接,于是向客戶端發(fā)送“確認包”,進入CLOSE_WAIT狀態(tài)。
注意:服務器收到請求后并不是立即斷開連接,而是先向客戶端發(fā)送“確認包”,告訴它我知道了,我需要準備一下才能斷開連接。 - 客戶端收到“確認包”后進入FIN_WAIT_2狀態(tài),等待服務器準備完畢后再次發(fā)送數(shù)據(jù)包。
- 等待片刻后,服務器準備完畢,可以斷開連接,于是再主動向客戶端發(fā)送 FIN 包,告訴它我準備好了,斷開連接吧。然后進入LAST_ACK狀態(tài)。
- 客戶端收到服務器的 FIN 包后,再向服務器發(fā)送 ACK 包,告訴它你斷開連接吧。然后進入TIME_WAIT狀態(tài)。
- 服務器收到客戶端的 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)絡是不穩(wěn)定的,隨時可能會毀壞數(shù)據(jù),所以機器A每次向機器B發(fā)送數(shù)據(jù)包后,都要求機器B”確認“,回傳ACK包,告訴機器A我收到了,這樣機器A才能知道數(shù)據(jù)傳送成功了。如果機器B沒有回傳ACK包,機器A會重新發(fā)送,直到機器B回傳ACK包。
客戶端最后一次向服務器回傳ACK包時,有可能會因為網(wǎng)絡問題導致服務器收不到,服務器會再次發(fā)送 FIN 包,如果這時客戶端完全關(guān)閉了連接,那么服務器無論如何也收不到ACK包了,所以客戶端需要等待片刻、確認對方收到ACK包后才能進入CLOSED狀態(tài)。那么,要等待多久呢?
數(shù)據(jù)包在網(wǎng)絡中是有生存時間的,超過這個時間還未到達目標主機就會被丟棄,并通知源主機。這稱為報文最大生存時間(MSL,Maximum Segment Lifetime)。TIME_WAIT 要等待 2MSL 才會進入 CLOSED 狀態(tài)。ACK 包到達服務器需要 MSL 時間,服務器重傳 FIN 包也需要 MSL 時間,2MSL 是數(shù)據(jù)包往返的最大時間,如果 2MSL 后還未收到服務器重傳的 FIN 包,就說明服務器已經(jīng)收到了 ACK 包
4.8.優(yōu)雅的斷開連接–shutdown()
close()/closesocket()和shutdown()的區(qū)別
確切地說,close() / closesocket() 用來關(guān)閉套接字,將套接字描述符(或句柄)從內(nèi)存清除,之后再也不能使用該套接字,與C語言中的 fclose() 類似。應用程序關(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)絡中發(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)絡模型層進行了劃分如下:

TCP/IP協(xié)議參考模型把所有的TCP/IP系列協(xié)議歸類到四個抽象層中
應用層:TFTP,HTTP,SNMP,F(xiàn)TP,SMTP,DNS,Telnet 等等
傳輸層:TCP,UDP
網(wǎng)絡層:IP,ICMP,OSPF,EIGRP,IGMP
數(shù)據(jù)鏈路層:SLIP,CSLIP,PPP,MTU
每一抽象層建立在低一層提供的服務上,并且為高一層提供服務,看起來大概是這樣子的


6、Socket常用函數(shù)接口及其原理
圖解socket函數(shù):


6.1、使用socket()函數(shù)創(chuàng)建套接字
int socket(int af, int type, int protocol);
- 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)常用到。 - type 為數(shù)據(jù)傳輸方式,常用的有 SOCK_STREAM 和 SOCK_DGRAM
- protocol 表示傳輸協(xié)議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協(xié)議和 UDP 傳輸協(xié)議
6.2、使用bind()和connect()函數(shù)
socket() 函數(shù)用來創(chuàng)建套接字,確定套接字的各種屬性,然后服務器端要用 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ù)
于服務器端程序,使用 bind() 綁定套接字后,還需要使用 listen() 函數(shù)讓套接字進入被動監(jiān)聽狀態(tài),再調(diào)用 accept() 函數(shù),就可以隨時響應客戶端的請求了。
通過** listen() 函數(shù)**可以讓套接字進入被動監(jiān)聽狀態(tài),它的原型為:
int listen(int sock, int backlog);
sock 為需要進入監(jiān)聽狀態(tài)的套接字,backlog 為請求隊列的最大長度。
所謂被動監(jiān)聽,是指當沒有客戶端請求時,套接字處于“睡眠”狀態(tài),只有當接收到客戶端請求時,套接字才會被“喚醒”來響應請求。
請求隊列
當套接字正在處理客戶端請求時,如果有新的請求進來,套接字是沒法處理的,只能把它放進緩沖區(qū),待當前請求處理完畢后,再從緩沖區(qū)中讀取出來處理。如果不斷有新的請求進來,它們就按照先后順序在緩沖區(qū)中排隊,直到緩沖區(qū)滿。這個緩沖區(qū),就稱為請求隊列(Request Queue)。
緩沖區(qū)的長度(能存放多少個客戶端請求)可以通過 listen() 函數(shù)的 backlog 參數(shù)指定,但究竟為多少并沒有什么標準,可以根據(jù)你的需求來定,并發(fā)量小的話可以是10或者20。
如果將 backlog 的值設置為 SOMAXCONN,就由系統(tǒng)來決定請求隊列長度,這個值一般比較大,可能是幾百,或者更多。
當請求隊列滿時,就不再接收新的請求,對于 Linux,客戶端會收到 ECONNREFUSED 錯誤
注意:listen() 只是讓套接字處于監(jiān)聽狀態(tài),并沒有接收請求。接收請求需要使用 accept() 函數(shù)。
當套接字處于監(jiān)聽狀態(tài)時,可以通過 accept() 函數(shù)來接收客戶端請求。它的原型為:
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
它的參數(shù)與 listen() 和 connect() 是相同的:sock 為服務器端套接字,addr 為 sockaddr_in 結(jié)構(gòu)體變量,addrlen 為參數(shù) addr 的長度,可由 sizeof() 求得。
accept() 返回一個新的套接字來和客戶端通信,addr 保存了客戶端的IP地址和端口號,而 sock 是服務器端的套接字,大家注意區(qū)分。后面和客戶端通信時,要使用這個新生成的套接字,而不是原來服務器端的套接字。
最后需要說明的是: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ù)。
前面我們說過,兩臺計算機之間的通信相當于兩個套接字之間的通信,在服務器端用 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)絡中傳輸數(shù)據(jù),而是先將數(shù)據(jù)寫入緩沖區(qū)中,再由TCP協(xié)議將數(shù)據(jù)從緩沖區(qū)發(fā)送到目標機器。一旦將數(shù)據(jù)寫入到緩沖區(qū),函數(shù)就可以成功返回,不管它們有沒有到達目標機器,也不管它們何時被發(fā)送到網(wǎng)絡,這些都是TCP協(xié)議負責的事情。
TCP協(xié)議獨立于 write()/send() 函數(shù),數(shù)據(jù)有可能剛被寫入緩沖區(qū)就發(fā)送到網(wǎng)絡,也可能在緩沖區(qū)中不斷積壓,多次寫入的數(shù)據(jù)被一次性發(fā)送到網(wǎng)絡,這取決于當時的網(wǎng)絡情況、當前線程是否空閑等諸多因素,不由程序員控制。
read()/recv() 函數(shù)也是如此,也從輸入緩沖區(qū)中讀取數(shù)據(jù),而不是直接從網(wǎng)絡中讀取

這些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套接字(默認情況下),當使用 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)絡發(fā)送數(shù)據(jù),那么輸出緩沖區(qū)會被鎖定,不允許寫入,write()/send() 也會被阻塞,直到數(shù)據(jù)發(fā)送完畢緩沖區(qū)解鎖,write()/send() 才會被喚醒。
3) 如果要寫入的數(shù)據(jù)大于緩沖區(qū)的最大長度,那么將分批寫入。
4) 直到所有數(shù)據(jù)被寫入緩沖區(qū) write()/send() 才能返回。
當使用 read()/recv() 讀取數(shù)據(jù)時:
1) 首先會檢查緩沖區(qū),如果緩沖區(qū)中有數(shù)據(jù),那么就讀取,否則函數(shù)會被阻塞,直到網(wǎng)絡上有數(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套接字默認情況下是阻塞模式