socket套接字及緩沖區詳解
文章目錄
一、域(domain)
二、類型(type)
三、協議(protocol)
四、socket緩沖區以及阻塞模式
1、緩沖區簡介
2、使用write()/send()發送數據
3、使用read()/recv()讀取數據
4、系統調用read()的返回錯誤場景
五、面試題--->TCP服務端一直sleep,客戶端發送數據問題
1、TCP發送數據的過程
2、阻塞方式的情況
3、非阻塞方式的情況
套接字是一種通信機制(通信的兩方的一種約定),憑借這種機制,不同主機之間的進程可以進行通信。我們可以用套接字中的相關函數來完成通信過程。
套接字的特性有三個屬性確定,它們是:域(domain),類型(type),和協議(protocol)。
#include<sys/types.h> #include<sys/socket.h> int socket(int domain, int type, int protocol);
一、域(domain)
域指定套接字通信中使用的網絡介質。最常見的套接字域是 AF_INET(IPv4)或者AF_INET6(IPV6),它是指 Internet 網絡,許多 Linux 局域網使用的都是該網絡,當然,因特網自身用的也是它。
二、類型(type)
流套接字(SOCK_STREAM):
流套接字用於提供面向連接、可靠的數據傳輸服務。該服務將保證數據能夠實現無差錯、無重復發送,並按順序接收。流套接字之所以能夠實現可靠的數據服務,原因在於其使用了傳輸控制協議,即TCP(The Transmission Control Protocol)協議。
數據報套接字(SOCK_DGRAM):
數據報套接字提供了一種無連接的服務。該服務並不能保證數據傳輸的可靠性,數據有可能在傳輸過程中丟失或出現數據重復,且無法保證順序地接收到數據。數據報套接字使用UDP(User Datagram Protocol)協議進行數據的傳輸。由於數據報套接字不能保證數據傳輸的可靠性,對於有可能出現的數據丟失情況,需要在程序中做相應的處理。
原始套接字(SOCK_RAW):
原始套接字與標准套接字(標准套接字指的是前面介紹的流套接字和數據報套接字)的區別在於:原始套接字可以讀寫內核沒有處理的IP數據包,而流套接字只能讀取TCP協議的數據,數據報套接字只能讀取UDP協議的數據。因此,如果要訪問其他協議發送數據必須使用原始套接字。
三、協議(protocol)
0:
使用默認協議;
IPPROTO_TCP;
使用TCP協議;
IPPROTO_UDP;
使用UDP協議;
四、socket緩沖區以及阻塞模式
1、緩沖區簡介
每個 socket 被創建后,都會分配兩個緩沖區,輸入緩沖區和輸出緩沖區。
write()/send() 並不立即向網絡中傳輸數據,而是先將數據寫入緩沖區中,再由TCP協議將數據從緩沖區發送到目標機器。一旦將數據寫入到緩沖區,函數就可以成功返回,不管它們有沒有到達目標機器,也不管它們何時被發送到網絡,這些都是TCP協議負責的事情。
read()/recv() 函數也是如此,也從輸入緩沖區中讀取數據,而不是直接從網絡中讀取。
I/O緩沖區在每個TCP套接字中單獨存在;
I/O緩沖區在創建套接字時自動生成;
即使關閉套接字也會繼續傳送輸出緩沖區中遺留的數據;
關閉套接字將丟失輸入緩沖區中的數據。
2、使用write()/send()發送數據
【阻塞模式下】:
首先會檢查緩沖區,如果緩沖區的可用空間長度小於要發送的數據,那么 write()/send() 會被阻塞(暫停執行),直到緩沖區中的數據被發送到目標機器,騰出足夠的空間,才喚醒 write()/send() 函數繼續寫入數據;
如果TCP協議正在向網絡發送數據,那么輸出緩沖區會被鎖定,不允許寫入,write()/send() 也會被阻塞,直到數據發送完畢緩沖區解鎖,write()/send() 才會被喚醒。如果TCP協議正在向網絡發送數據,那么輸出緩沖區會被鎖定,不允許寫入,write()/send() 也會被阻塞,直到數據發送完畢緩沖區解鎖,write()/send() 才會被喚醒;
如果要寫入的數據大於緩沖區的最大長度,那么將分批寫入。如果要寫入的數據大於緩沖區的最大長度,那么將分批寫入;直到所有數據被寫入緩沖區 write()/send() 才能返回。直到所有數據被寫入緩沖區 write()/send() 才能返回。
send()函數默認情況下會使用Nagle算法。Nagle算法通過將未確認的數據存入緩沖區直到積攢到一定數量一起發送的方法,來降低主機發送零碎小數據包的數目。所以假設send()函數發送數據過快的話,該算法會將一些數據打包后統一發出去。通過setsockopt()的TCP_NODELAY選項來禁用Nagle算法。
【非阻塞模式下】:
send()函數的過程僅僅是將數據拷貝到協議棧的緩沖區而已,如果緩沖區可用空間不夠,則盡可能拷貝,返回成功拷貝的大小;如果緩存區可用空間為0,則返回-1,同時設置errno為EAGAIN。
3、使用read()/recv()讀取數據
【阻塞模式下】:
首先會檢查緩沖區,如果緩沖區中有數據,那么就讀取,否則函數會被阻塞,直到網絡上有數據到來;
如果要讀取的數據長度小於緩沖區中的數據長度,那么就不能一次性將緩沖區中的所有數據讀出,剩余數據將不斷積壓,直到讀取到數據后 read()/recv() 函數才會返回,否則就一直被阻塞。
【非阻塞模式下】:
接收數據時perror時常遇到“Resource temporarilyunavailable”的提示,errno代碼為11(EAGAIN)。這表明你在非阻塞模式下調用了阻塞操作,在該操作沒有完成就返回這個錯誤,這個錯誤不會破壞socket的同步,繼續循環接着recv就可以。
4、系統調用read()的返回錯誤場景
EINTR:在讀取到數據以前調用被信號所中斷。
EAGAIN:使用 O_NONBLOCK 標志指定了非阻塞式輸入輸出,但當前沒有數據可讀或者使用了阻塞操作。
EIO:輸入輸出錯誤.可能是正處於后台進程組進程試圖讀取其控制終端,但讀操作無效,或者被信號SIGTTIN所阻塞,或者其進程組是孤兒進程組.也可能執行的是讀磁盤或者磁帶機這樣的底層輸入輸出錯誤。
EISDIR:fd 指向一個目錄。
EBADF:fd 不是一個合法的文件描述符,或者不是為讀操作而打開。
EINVAL:fd 所連接的對象不可讀。
EFAULT:buf 超出用戶可訪問的地址空間。
EWOULDBLOCK:用於非阻塞模式,表示不需要重新讀或者寫。
五、面試題—>TCP服務端一直sleep,客戶端發送數據問題
1、TCP發送數據的過程
TCP發送數據的大體過程:首先,TCP是有鏈接的可靠傳輸協議,所謂可靠也就是說保證客戶端發送的數據服務端都能夠收到,並且是按序收到。那么對於上面的問題就不可能存在數據的丟棄。那么客戶端一直發送數據越來越多怎么辦?下面我們分析一下TCP的傳輸過程。
1. 數據首先由應用程序緩沖區復制到發送端的輸出緩沖區(位於內核),注意這個過程是用類似write功能的函數完成的。有的人通常看到write成功就以為數據發送到了對端主機,其實這是錯誤的,write成功僅僅表示數據成功的由應用進程緩沖區復制到了輸出緩沖區。
2. 然后內核協議棧將輸出緩沖區中的數據發送到對端主機,注意這個過程不受應用程序控制,而是發送端內核協議棧完成,其中包括使用滑動窗口、擁塞控制等功能。
3. 數據到達接收端主機的輸入緩沖區,注意這個接收過程也不受應用程序控制,而是由接收端內核協議棧完成,其中包括發送ack確認等。
4. 數據由套接字接收緩沖區復制到接收端應用程序緩沖區,注意這個過程是由類似read等函數來完成。
2、阻塞方式的情況
阻塞方式下,如果服務端一直sleep不接收數據,而客戶端一直write,也就是只能執行上述過程中的前三步,這樣最終結果肯定是接收端的輸入緩沖區和發送端的輸出緩沖區都被填滿,這樣write就無法繼續將數據從應用程序復制到發送端的輸出緩沖區了,從而使進程進入睡眠。
3、非阻塞方式的情況
非阻塞情況下,服務端一直sleep,客戶端一直write數據的結果:開始客戶端write成功,隨着客戶端write,接收端的輸入緩沖區和發送端的輸出緩沖區會被填滿。當發送端的輸出緩沖區的可用空間小於write請求寫的字節數時,write立即返回-1,並將errno置為EWOULDBLOCK。
UDP的接收緩沖區:
每個UDP Socket都有一個接收緩沖區,沒有發送緩沖區。有數據就直接發送,不管對方是否能夠正確接收,也不管對端接收緩沖區是否已經滿了。
UDP是沒有流量控制的;快的發送者可以很容易地就淹沒慢的接收者,導致接收方的UDP丟棄數據報
參考:
https://www.cnblogs.com/kex1n/p/6501977.html
https://blog.csdn.net/lianghe_work/article/details/45170359
http://blog.chinaunix.net/uid-28541347-id-4730278.html
https://www.cnblogs.com/suanec/p/4248207.html