1. 程式人生 > >認識recv()與send()

認識recv()與send()

 首先先總結下send函式的相關內容。

send函式

函式功能

 send函式可以用於伺服器向客戶端傳送資料,同樣也可以使客戶端向伺服器傳送資料。

原型

int send( SOCKET s_fd, const char* sendBuf, int len, int flags );

引數說明

 1、s_fd: 指的是傳送端的套接字描述符。即當伺服器傳送時,指accept到的描述符;當客戶端傳送時指connect到的描述符;
 2、sendBuf: 指容納傳送內容的緩衝區,需要知道的是該部分緩衝區是指我們上層程式編寫中自己定義的緩衝區;
 3、len: 表示sendBuf緩衝區中內容的長度,即要傳送內容的位元組數;
 4、flags

: 一般這裡我們寫0,但情況這裡做一個簡單說明:
  0: 與write()無異;
  MSG_DONTROUTE:告訴核心,目標主機在本地網路,不用查路由表;
  MSG_DONTWAIT:將單個I/O操作設定為非阻塞模式;
  MSG_OOB:指明發送的是帶外資訊;

send函式的執行流程

 為了更簡單地敘述和了解send函式的執行流程,我們這裡通過同步的執行流程進行說明。
 這裡順帶簡單說一下同步非同步
  同步:可以說是在發出一個功能呼叫時,在沒有得到結果之前,該呼叫就不返回;
  非同步:呼叫者不能立刻得到結果。實際處理這個呼叫的部件在完成後,通過狀態、通知和回撥來通知呼叫者。
 好了,說了下題外話,迴歸正題。send函式在同步執行時的流程。
 這裡的執行流程中進行了兩次比較,兩次判斷與一次協議執行,具體如下:
第一次比較

:比較套接字s_fd的傳送緩衝區位元組長度與len長度。len長度(sendBuf中傳送位元組的長度)大於套接字s_fd的傳送緩衝區長度使,則該函式返回SOCKET_ERROR;否則,則進入下一次判斷;(注意這裡s_fd的傳送緩衝區是指底層的緩衝區,這個緩衝區不能在我們的上層程式中進行定義修改)
第一次判斷:檢查判斷協議是否是在傳送傳送緩衝區的資料,如果是,則等待其傳送完成;如果還沒開始傳送或者傳送緩衝區中沒有資料,則進入第二次的比較;
第二次比較:判斷sendBuf中資料內容長度,即len的值與傳送緩衝區的的剩餘長度,如果len值大於傳送緩衝區剩餘長度,則等待其資料傳送完;如果len值小於傳送緩衝區的剩餘空間則send函式僅僅將要傳送的資料內容拷貝入傳送緩衝區中
,然後進入最後的第二次判斷;
第二次判斷:判斷是否傳送成功,如果成功返回值為傳送的實際位元組數,如果在拷貝過程中出現錯誤則返回SOCKET_ERROR,如果send在等待協議將傳送緩衝區資料傳送的過程中出現網路斷開的情況,也返回SOCKET_ERROR。
一次協議執行:當資料在協議傳送過程中傳送出現網路錯誤的話,會返回SOCKET_ERROR,但該返回值會在下一次呼叫socket相關函式時進行返回

!!!注意:這裡需要注意的是,send函式只是僅僅將要傳送的內容拷貝到緩衝區,並沒有進行實質性的傳送操作,而實質性的傳送操作是由傳送協議進行的。所以這裡是當send函式將資料成功拷貝到傳送緩衝區後就返回了,所以得到send函式的返回值並不代表資料已經發送成功了。
!!!注意:在Unix系統下,如果send在等待協議傳送資料時網路斷開的話,呼叫send的程序會接收到一個SIGPIPE訊號,程序對該訊號的預設處理是程序終止。

返回值

 含有兩種情況的返回值:
 1、返回值大於0:指傳送send拷貝到傳送緩衝區成功,返回拷貝成功的位元組數;
 2、返回值小於0:傳送失敗,錯誤原因存於全域性變數errno中,具體如下:
  錯誤程式碼:
  EBADF 引數s 非合法的socket處理程式碼。
  EFAULT 引數中有一指標指向無法存取的記憶體空間
  ENOTSOCK 引數s為一檔案描述詞,非socket。
  EINTR 被訊號所中斷。
  EAGAIN 此操作會令程序阻斷,但引數s的socket為不可阻斷。
  ENOBUFS 系統的緩衝記憶體不足
  ENOMEM 核心記憶體不足
  EINVAL 傳給系統呼叫的引數不正確。

recv函式

函式功能

 該函式的功能為接收發送端發過來的資料,接收端可以使客戶端或者是伺服器端,傳送端也一樣。

函式原型及引數

原型

int recv( SOCKET s_fd, char* recvBuf, int len, int flags);

recv引數

s_fd:socket描述符,指定接收端套接字描述符;
recvBuf:接收緩衝區,需要注意的是該緩衝區為上層程式編寫時自己定義的緩衝區;
len:接收緩衝區所能容納的位元組數;
flags:一般置0,簡單介紹如下:
  0:常規操作,與read()相同;
  MSG_DONTWAIT:將單個I/O操作設定為非阻塞模式;
  MSG_OOB:指明發送的是帶外資訊;
  MSG_PEEK:可以檢視可讀的資訊,在接收資料後不會將這些資料丟失;
  MSG_WAITALL:通知核心直到讀到請求的資料位元組數時,才返回。
 一張其他地方引用的表格也可以很清楚地瞭解send函式和recv函式flags的取值。引用網址:http://www.cnblogs.com/blankqdb/archive/2012/08/30/2663859.html

引用塊內容

recv執行流程

 與send有所不同,recv的執行過程比較的步驟較少,但等待判斷加多。
 首先,執行recv時會先對s_fd套接字的傳送緩衝區進行判斷,沒錯是傳送緩衝區,他需要先判斷該套接字的傳送緩衝區內的資料是否傳送完畢,如果沒有傳送完畢則等待其傳送完畢;如果傳送完畢則接下去進行。在此過程中,如果協議在傳送過程中出錯則返回SOKET_ERROR;
 接下來,才是判斷s_fd套接字的接收緩衝區是否正在進行接收或者接收緩衝區中沒有資料,則一直等待,直到協議將資料接收完畢;
 然後,recv函式便將底層的接收緩衝區的函式拷貝到上層程式定義的recvBuf緩衝區中,這裡需要注意的是recv函式做的僅僅是將底層的接收緩衝區資料拷貝到上層定義的緩衝區中,至於資料的接收是通過協議來執行的;同時還要注意的是,有時候一份完整的資料可能會大於recvBuf緩衝區,所以需要多次的recv才能拷貝完整;
 最後如果recv接收資料成功,則返回接收到的位元組數;如果在向上層拷貝時出錯則返回SOCKET_ERROR;如果recv函式在等待協議接收資料時網路中斷了,那麼它返回0。

 與send函式相同,在Unix系統下,如果recv函式在等待協議接收資料時網路斷開了,那麼呼叫recv的程序會接收到一個SIGPIPE訊號,程序對該訊號的預設處理是程序終止。

關於返回值

 這裡recv函式預設情況下是阻塞的,如果需要用到非阻塞可以通過其他方法進行處理,比如使用select介面。
 但不管是否是阻塞的,其返回值都是一樣的。
 1、返回值大於0:接收成功,返回接收到的資料的位元組數;
 2、返回值等於0:表示另一端已關閉連線則返回0,這種關閉是對方主動且正常的關閉;
 3、返回值小於0:表示接收出錯,但一種情況很特別,如下引用:

特別地:返回值<0時並且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況下認為連線是正常的,繼續接收。
只是阻塞模式下recv會一直阻塞直到接收到資料,非阻塞模式下如果沒有資料就會返回,不會阻塞著讀,因此需要迴圈讀取)。

 其他出錯小於0返回值的說明:
  EAGAIN:套接字已標記為非阻塞,而接收操作被阻塞或者接收超時
  EBADF:sock不是有效的描述詞
  ECONNREFUSE:遠端主機阻絕網路連線
  EFAULT:記憶體空間訪問出錯
  EINTR:操作被訊號中斷
  EINVAL:引數無效
  ENOMEM:記憶體不足
  ENOTCONN:與面向連線關聯的套接字尚未被連線上
  ENOTSOCK:sock索引的不是套接字