1. send函數
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
參數:sockfd是socket()的返回值,文件描述符;buf是待發送數據所在的數據區的指針;len是發送數據的長度;flags標志位,默認為0。
返回值:(阻塞與非阻塞沒有區別)>0表示成功將數據復制到緩沖區中,返回的值表示發送的字節數;=0表示對方主動關閉了連接過程;<0表示出錯,會返回SOCKET_ERROR。
send發送數據實際上是將數據(應用層buf中的數據)拷貝到套接字sockfd的緩沖區(內核中的sockfd對應的發送緩沖區)中,內核中的發送緩存中的數據由協議(TCP,UDP沒有發送緩沖區)傳輸。send函數將buf中的數據成功拷貝到發送緩沖區后就返回了,如果協議后續發送數據到接收端出現網絡錯誤的話,那么下一個socket函數就會返回SOCKET_ERROR。
send發送數據時,首先比較待發送的數據長度len和套接字sockfd的發送緩沖區的長度,
(1)如果待發送數據的長度len大於sockfd發送緩沖區的長度,則返回SOCKET_ERROR;
(2)如果待發送數據的長度len小於等於sockfd發送緩沖區的長度,則再檢查協議是否正在發送sockfd的發送緩沖區的數據,
【1】如果正在發送,則等協議把發送緩沖區中的數據發送完畢后(這里應該有阻塞和非阻塞的區別,阻塞的話等待數據的發送完畢,非阻塞的話立即返回,並將errno置為EAGAIN),將待發送的數據拷貝到sockfd的發送緩沖區中;
【2】如果沒有在發送數據,或發送緩沖區中沒有數據,則比較sockfd的發送緩沖區的剩余空間和待發送數據的長度len:
A. 如果剩余空間大於待發送數據的長度len,則將buf里的數據拷貝到剩余空間里;
B. 如果剩余空間小於待發送數據的長度len,則等待協議把sockfd的發送緩沖區中的數據發送騰出空間(收到接收方的確認)(阻塞與非阻塞的區別,見ps的1),再將待發送數據拷貝到發送緩沖區中。
PS:1. 如果剩余空間小於待發送數據的長度len,阻塞socket會等待協議將發送緩沖區中的數據發送(緩沖區應該要收到接收方的確認之后,才能騰出空間),再拷貝待發送的數據並返回;非阻塞socket會盡力拷貝(能拷多少拷多少),返回已拷貝字節的大小,如果緩沖區可用空間為0,則返回-1,並置errno為EAGAIN((不確定)。
2. TCP有發送緩沖區,數據發送是協議發送發送緩沖區的內容;UDP沒有發送緩沖區,UDP有數據要發送時,直接發送到網絡上,不會緩存。
3. 接收端的sockfd緩沖區(內核的緩沖區)收到數據包后,就會返回ACK,不會等待recv到用戶空間再返回。
2. recv函數
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
參數:sockfd是socket()的返回值,文件描述符;buf是接受數據的緩存區的指針;len是發送數據的長度;flags標志位,默認為0。
返回值:(阻塞與非阻塞沒有區別)>0表示成功,此時的值是接收到的數據大小;=0表示對方調用了close API來關閉連接;<0表示出錯,<0且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況下認為連接是正常的,繼續接收,此時,errno被設為下面的某個值:
EAGAIN:套接字已標記為非阻塞,而接收操作被阻塞或者接收超時,連接正常,繼續接收
EBADF:sock不是有效的描述詞
ECONNREFUSE:遠程主機阻絕網絡連接
EFAULT:內存空間訪問出錯
EINTR:操作被信號中斷,連接正常,繼續接收
EINVAL:參數無效
ENOMEM:內存不足
ENOTCONN:與面向連接關聯的套接字尚未被連接上
ENOTSOCK:sock索引的不是套接字
recv僅僅將接收到的數據(存儲在內核的接受緩沖區)拷貝到buf(應用層用戶的緩存區)中,真正的接受數據是由協議(TCP/UDP,UDP調用的函數名稱不同)完成的。
recv先檢查sockfd的發送緩沖區中有沒有數據:
(1)如果發送緩沖區中有數據,先等待sockfd的發送緩沖區的數據被協議發送完畢;如果協議在傳送發送緩沖區的數據時出現網絡錯誤,則返回SOCKET_ERROR;
(2)如果sockfd發送緩沖區中的數據發送完畢或者發送緩沖區中沒有數據,則檢查sockfd的接收緩沖區,如果接受緩沖區中沒有數據或者協議正在接收數據,那么recv一直等待(阻塞socket將等待,非阻塞socket直接返回-1,errno置為EWOULDBLOCK),直到協議將數據接受完畢;當協議把數據接收完畢,recv函數就把sockfd的接受緩沖區中的數據拷貝到buf中,然后返回拷貝的字節數。(注:協議接收到的數據的長度可能大於buf的長度,此時要調用幾次recv函數才能把sockfd接受緩沖區中的數據拷貝完。)
3. 阻塞與非阻塞的區別
發送時:在發送緩沖區的空間大於待發送數據的長度的條件下,阻塞socket一直等到有足夠的空間放待發送的數據,將數據拷貝到發送緩沖區中才返回;非阻塞socket在沒有足夠空間時,會拷貝部分,並返回已拷貝的字節數,置errno為EWOULDBLOCK(不確定置為啥)。
接收時:如果sockfd發送緩沖區中有數據,或接受緩沖區中無數據,或協議正在接受數據,阻塞socket都將等待,直到有數據可以拷貝到用戶程序中;非阻塞socket會返回-1,置errno為EWOULDBLOCK,表示“沒有數據,回頭來看”。
4. TCP與UDP的區別,TCO如何使用緩沖區實現流量控制
TCP有發送緩沖區和接收緩沖區;UDP只有接受緩沖區,UDP發送時不緩存,直接發送出去。對於接收緩沖區,TCP和UDP的recv操作相同,分為阻塞與非阻塞socket。
TCP的sockfd的接收緩沖區如果滿了之后,接收端通知發送端,接收窗口關閉(win=0),保證了TCP套接口接收緩沖區不溢出,從而實現可靠傳輸;如果發送方無視窗口大小,仍然發送,則接收方TCP丟失收到的包。這就是TCP的流量控制(流量控制是點對點的)
UDP的接受緩沖區滿了之后,對方並不知道,新來的數據報無法進入緩沖區,直接被丟棄,所以UDP沒有流量控制,快的發送者可以將慢的接收方淹沒,導致接收方丟棄數據包。
5. send和recv圖示