『壹』 linux socket 非阻塞模式編程,客戶端連接斷掉或者異常,為什麼還能write成功,返回大於0
客戶端連接斷掉,不見得會立即被網卡驅動檢測到,數據只是存入本地的寫緩沖區,不代表對端就收到了。
在系統實時性要求不高的情況下,可以採用心跳檢測機制,客戶端每秒鍾發送一個KeepAlive消息給服務端,連續5s沒收到服務端就可以認為連接斷開了。
『貳』 linux網路編程,為什麼要將文件描述符設置成非阻塞模式
非阻塞IO 和阻塞IO:
在網路編程中對於一個網路句柄會遇到阻塞IO 和非阻塞IO 的概念, 這里對於這兩種socket 先做一下說明:
基本概念:
阻塞IO::
socket 的阻塞模式意味著必須要做完IO 操作(包括錯誤)才會
返回。
非阻塞IO::
非阻塞模式下無論操作是否完成都會立刻返回,需要通過其他方
式來判斷具體操作是否成功。(對於connect,accpet操作,通過select判斷,
對於recv,recvfrom,send,sendto通過返回值+錯誤碼來判斷)
IO模式設置:
SOCKET
對於一個socket 是阻塞模式還是非阻塞模式的處理方法::
方法::
用fcntl 設置;用F_GETFL獲取flags,用F_SETFL設置flags|O_NONBLOCK;
同時,recv,send 時使用非阻塞的方式讀取和發送消息,即flags設置為MSG_DONTWAIT
實現
fcntl 函數可以將一個socket 句柄設置成非阻塞模式:
flags = fcntl(sockfd, F_GETFL, 0); //獲取文件的flags值。
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //設置成非阻塞模式;
flags = fcntl(sockfd,F_GETFL,0);
fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK); //設置成阻塞模式;
並在接收和發送數據時:
將recv, send 函數的最後有一個flag 參數設置成MSG_DONTWAIT
recv(sockfd, buff, buff_size,MSG_DONTWAIT); //非阻塞模式的消息發送
send(scokfd, buff, buff_size, MSG_DONTWAIT); //非阻塞模式的消息接受
普通文件
對於文件的阻塞模式還是非阻塞模式::
方法1、open時,使用O_NONBLOCK;
方法2、fcntl設置,使用F_SETFL,flags|O_NONBLOCK;
消息隊列
對於消息隊列消息的發送與接受::
//非阻塞 msgsnd(sockfd,msgbuf,msgsize(不包含類型大小),IPC_NOWAIT)
//阻塞 msgrcv(scokfd,msgbuf,msgsize(**),msgtype,IPC_NOWAIT);
讀
阻塞與非阻塞讀的區別: //阻塞和非阻塞的區別在於沒有數據到達的時候是否立刻返回.
讀(read/recv/msgrcv):
讀的本質來說其實不能是讀,在實際中, 具體的接收數據不是由這些調用來進行,是由於系統底層自動完成的。read 也好,recv 也好只負責把數據從底層緩沖 到我們指定的位置.
對於讀來說(read, 或者recv) ::
阻塞情況下::
在阻塞條件下,read/recv/msgrcv的行為::
1、如果沒有發現數據在網路緩沖中會一直等待,
2、當發現有數據的時候會把數據讀到用戶指定的緩沖區,但是如果這個時候讀到的數據量比較少,比參數中指定的長度要小,read 並不會一直等待下去,而是立刻返回。
read 的原則::是數據在不超過指定的長度的時候有多少讀多少,沒有數據就會一直等待。
所以一般情況下::我們讀取數據都需要採用循環讀的方式讀取數據,因為一次read 完畢不能保證讀到我們需要長度的數據,
read 完一次需要判斷讀到的數據長度再決定是否還需要再次讀取。
非阻塞情況下::
在非阻塞的情況下,read 的行為::
1、如果發現沒有數據就直接返回,
2、如果發現有數據那麼也是採用有多少讀多少的進行處理.
所以::read 完一次需要判斷讀到的數據長度再決定是否還需要再次讀取。
對於讀而言:: 阻塞和非阻塞的區別在於沒有數據到達的時候是否立刻返回.
recv 中有一個MSG_WAITALL 的參數::
recv(sockfd, buff, buff_size, MSG_WAITALL),
在正常情況下recv 是會等待直到讀取到buff_size 長度的數據,但是這里的WAITALL 也只是盡量讀全,在有中斷的情況下recv 還是可能會被打斷,造成沒有讀完指定的buff_size的長度。
所以即使是採用recv + WAITALL 參數還是要考慮是否需要循環讀取的問題,在實驗中對於多數情況下recv (使用了MSG_WAITALL)還是可以讀完buff_size,
所以相應的性能會比直接read 進行循環讀要好一些。
注意:: //使用MSG_WAITALL時,sockfd必須處於阻塞模式下,否則不起作用。
//所以MSG_WAITALL不能和MSG_NONBLOCK同時使用。
要注意的是使用MSG_WAITALL的時候,sockfd 必須是處於阻塞模式下,否則WAITALL不能起作用。
寫
阻塞與非阻塞寫的區別: //
寫(send/write/msgsnd)::
寫的本質也不是進行發送操作,而是把用戶態的數據 到系統底層去,然後再由系統進行發送操作,send,write返回成功,只表示數據已經 到底層緩沖,而不表示數據已經發出,更不能表示對方埠已經接收到數據.
對於write(或者send)而言,
阻塞情況下:: //阻塞情況下,write會將數據發送完。(不過可能被中斷)
在阻塞的情況下,是會一直等待,直到write 完,全部的數據再返回.這點行為上與讀操作有所不同。
原因::
讀,究其原因主要是讀數據的時候我們並不知道對端到底有沒有數據,數據是在什麼時候結束發送的,如果一直等待就可能會造成死循環,所以並沒有去進行這方面的處理;
寫,而對於write, 由於需要寫的長度是已知的,所以可以一直再寫,直到寫完.不過問題是write 是可能被打斷嗎,造成write 一次只write 一部分數據, 所以write 的過程還是需要考慮循環write, 只不過多數情況下一次write 調用就可能成功.
非阻塞寫的情況下:: //
非阻塞寫的情況下,是採用可以寫多少就寫多少的策略.與讀不一樣的地方在於,有多少讀多少是由網路發送的那一端是否有數據傳輸到為標准,但是對於可以寫多少是由本地的網路堵塞情況為標準的,在網路阻塞嚴重的時候,網路層沒有足夠的內存來進行寫操作,這時候就會出現寫不成功的情況,阻塞情況下會盡可能(有可能被中斷)等待到數據全部發送完畢, 對於非阻塞的情況就是一次寫多少算多少,沒有中斷的情況下也還是會出現write 到一部分的情況.
『叄』 Linux內核(六)阻塞&非阻塞
阻塞和非阻塞IO是訪問設備的兩種模式,驅動程序可以靈活的支持者兩種用戶空間對設備的訪問方式。
如下圖所示,在阻塞訪問時,不能獲取資源的進程將進入休眠,它將會讓出CPU,因為阻塞的進程會進入休眠狀態,所以必須要有一個動作能喚醒該進程,喚醒進程的地方最大的可能發生在中斷裡面,因為在硬體資源獲得的同時往往伴隨著一個中斷。而非阻塞的進程則不斷的嘗試,直到可以進行IO。
在Linux驅動程序中,可以使用等待隊列(Wait Queue)來實現阻塞隊列的喚醒。等待隊列以隊列為基礎數據結構,與進程調度機制緊密結合,可以用來同步對系統資源的訪問,
『肆』 linux阻塞與非阻塞I/O
阻塞操作是指在執行設備操作時,若不能獲得資源,則掛起進程直到滿足可操作的條件後再進行操作。被掛起的進程進入睡眠狀態,被從調度器的運行隊列移走,直斗數到等待的條茄耐件被滿足。而非阻塞操作的進程在不能進行設備操作時,並不掛起,它要麼放棄,要麼不停地查詢,直至可以進行操作為止。
驅動程序通常需要提供這樣的能力:當應用程序進行read()、write()等系統調用時,若設備的資源不能獲取,而用戶又希望以阻塞的方式訪問設備,驅動程序應在設備驅動顫銷春的xxx_read()、xxx_write()等操作中將進程阻塞直到資源可以獲取,此後,應用程序的read()、write()等調用才返回,整個過程仍然進行了正確的設備訪問,用戶並沒有感知到;若用戶以非阻塞的方式訪問設備文件,則當設備資源不可獲取時,設備驅動的xxx_read()、xxx_write()等操作應立即返回,read()、write()等系統調用也隨即被返回,應用程序收到-EAGAIN返回值。
在阻塞訪問時,不能獲取資源的進程將進入休眠,它將CPU資源「禮讓」給其他進程。因為阻塞的進程會進入休眠狀態,所以必須確保有一個地方能夠喚醒休眠的進程,否則,進程就真的「壽終正寢」了。喚醒進程的地方最大可能發生在中斷裡面,因為在硬體資源獲得的同時往往伴隨著一個中
斷。而非阻塞的進程則不斷嘗試,直到可以進行I/O。
『伍』 linux下阻塞,非阻塞,輪詢
非阻塞並不是不斷的輪詢,否則和阻塞也沒有什麼區別,而是直接返回,讓你回自己去處理請求不能滿足的情況答,就是你自己看到沒有位置給你停車了,自己做決定是繼續等待(循環非阻塞調用,當然沒有必要,因為可以直接阻塞調用),還是去想別的辦法停車。
至於佔用的 cpu 資源應該相差不大,阻塞之後當前線程或進程放棄系統執行時間,直到條件滿足,阻塞期間應該就是不會消耗太多資源,因為執行時間都不佔用了。而非阻塞就只是一個調用而已,不滿足就直接返回到當前程序了,所以兩個應該差不多的。
平時用非阻塞的時候應該就是希望程序自己處理請求不能滿足的情況了,而阻塞則是程序只有滿足條件才可以繼續執行,所以就一直阻塞等待著了,那個用多用少看使用的情況而定了。因為本身就是區別很明顯的,功能並沒交叉。
『陸』 linux下send命令是干什麼用的
功能描述:
發送消息,send只可用於基於連接的套接字,send 和 write唯一的不同點是標志的存在,當標志為0時,send等同於write。sendto 和 sendmsg既可用於無連接的套接字,也可用於基於連接的套接字。除了套接字設置為非阻塞模式,調用將會阻塞直到數據被發送完。
參數:
sock:索引將要從其發送數據的套接字。
buf:指向將要發送數據的緩沖區。
len:以上緩沖區的長度。
flags:是以下零個或者多個標志的組合體,可通過or操作連在一起
MSG_DONTROUTE:不要使用網關來發送封包,只發送到直接聯網的主機。這個標志主要用於診斷或者路由程序。
MSG_DONTWAIT:操春喚作不會被阻塞。
MSG_EOR:終止一個記錄。
MSG_MORE:調用者有更多的數據需要發送。
MSG_NOSIGNAL:當告胡另一端終止連接時,請求在基於流的錯誤套襪森攔接字上不要發送SIGPIPE信號。
MSG_OOB:發送out-of-band數據(需要優先處理的數據),同時現行協議必須支持此種操作。
to:指向存放接收端地址的區域,可以為NULL。
tolen:以上內存區的長度,可以為0。
『柒』 linux網路編程中阻塞和非阻塞socket的區別
讀操作
對於阻塞的socket,當socket的接收緩沖區中沒有數據時,read調用會一直阻塞住,直到有數據到來才返
回。當socket緩沖區中的數據量小於期望讀取的數據量時,返回實際讀取的位元組數。當sockt的接收緩沖
區中的數據大於期望讀取的位元組數時,讀取期望讀取的位元組數,返回實際讀取的長度。
對於非阻塞socket而言,socket的接收緩沖區中有沒有數據,read調用都會立刻返回。接收緩沖區中有
數據時,與阻塞socket有數據的情況是一樣的,如果接收緩沖區中沒有數據,則返回錯誤號為
EWOULDBLOCK,
表示該操作本來應該阻塞的,但是由於本socket為非阻塞的socket,因此立刻返回,遇到這樣的情況,可
以在下次接著去嘗試讀取。如果返回值是其它負值,則表明讀取錯誤。
因此,非阻塞的rea調用一般這樣寫:
if
((nread
=
read(sock_fd,
buffer,
len))
<
0)
{
if
(errno
==
EWOULDBLOCK)
{
return
0;
//表示沒有讀到數據
}else
return
-1;
//表示讀取失敗
}else
return
nread;讀到數據長度
寫操作
對於寫操作write,原理是類似的,非阻塞socket在發送緩沖區沒有空間時會直接返回錯誤號EWOULDBLOCK,
表示沒有空間可寫數據,如果錯誤號是別的值,則表明發送失敗。如果發送緩沖區中有足夠空間或者
是不足以拷貝所有待發送數據的空間的話,則拷貝前面N個能夠容納的數據,返回實際拷貝的位元組數。
而對於阻塞Socket而言,如果發送緩沖區沒有空間或者空間不足的話,write操作會直接阻塞住,如果有
足夠空間,則拷貝所有數據到發送緩沖區,然後返回.
非阻塞的write操作一般寫法是:
int
write_pos
=
0;
int
nLeft
=
nLen;
while
(nLeft
>
0)
{
int
nWrite
=
0;
if
((nWrite
=
write(sock_fd,
data
+
write_pos,
nLeft))
<=
0)
{
if
(errno
==
EWOULDBLOCK)
{
nWrite
=
0;
}else
return
-1;
//表示寫失敗
}
nLeft
-=
nWrite;
write_pos
+=
nWrite;
}
return
nLen;
建立連接
阻塞方式下,connect首先發送SYN請求道伺服器,當客戶端收到伺服器返回的SYN的確認時,則
connect
返回.否則的話一直阻塞.
非阻塞方式,connect將啟用TCP協議的三次握手,但是connect函數並不等待連接建立好才返回,而是
立即返回。返回的錯誤碼為EINPROGRESS,表示正在進行某種過程.
接收連接
對於阻塞方式的傾聽socket,accept在連接隊列中沒有建立好的連接時將阻塞,直到有可用的連接,才返
回。
非阻塞傾聽socket,在有沒有連接時都立即返回,沒有連接時,返回的錯誤碼為EWOULDBLOCK,表示本來應
該阻塞。
無阻塞的設置方法
方法一:fcntl
int
flag;
if
(flag
=
fcntl(fd,
F_GETFL,
0)
<0)
perror("get
flag");
flag
|=
O_NONBLOCK;
if
(fcntl(fd,
F_SETFL,
flag)
<
0)
perror("set
flag");
方法二:ioctl
int
b_on
=
1;
ioctl
(fd,
FIONBIO,
&b_on);
『捌』 linux手冊翻譯——send(2)
send, sendto, sendmsg - send a message on a socket
系統調用 send()、sendto() 和 sendmsg() 用於將消息傳輸到另一個套接字。
僅當套接字處於連接狀態時才可以使用 send() 調用(以便知道預期的接收者, 也就是說send()僅僅用於數據流類型的數據發送 ,對於TCP,服務端和客戶端都可以使用send/recv;但是對於UDP,只能是客戶端使用send/recv,服務端只能使用sendto/recvfrom,因為客戶端是進行了connect操作知道要發送和接受的地址)。send() 和 write(2) 之間的唯一滲衡區別是存在 flags 參數。此外,
send(sockfd, buf, len, flags);
等價於
sendto(sockfd, buf, len, flags, NULL, 0);
參數 sockfd 是發送者套接字的文件描述符。
如果在連接模式的套接字(即套接字類型為SOCK_STREAM、SOCK_SEQPACKET)上使用 sendto(),則參數 dest_addr 和 addrlen 將被忽略(當它們不是NULL和0時可能返回錯誤EISCONN),若套接字沒有實際連接(還沒有三次握手建立連接)將返回錯誤ENOTCONN。 否則,目標地址由 dest_addr 給出, addrlen 指定其大小。 對於 sendmsg(),目標地址由 msg.msg_name 給出, msg.msg_namelen 指定其大小。
對於 send() 和 sendto(),消息位於 buf 中,長度為 len 。 對於sendmsg(),消息存放於 msg.msg_iov 元素指向 數組數據區 (見下)中。 sendmsg() 調用還允許發送輔助數據(也稱為控制信息) 。
如果消息太長而無法通過底層協議原子傳遞( too long to pass atomically through the underlying protocol ),則返回錯誤 EMSGSIZE,並且不會傳輸消息。
No indication of failure to deliver is implicit in a send(). Locally detected errors are indicated by a return value of -1.
當消息轎陵不適合套接字的發送緩沖區時,send() 通常會阻塞,除非套接字已置於非阻塞 I/O 模式。 在這種情況下,在非阻塞模式下它會失敗並顯示錯誤 EAGAIN 或 EWOULDBLOCK。 select(2) 調用可用於確定何時可以發送更多數據 。
上面的的描述還是很籠統的,以TCP為例,按我的理解,我認為只要發送緩沖區有空閑位置,且此時協議棧沒有向網路發送數據,那麼就可以寫入,對於阻塞模式,直到所有數據寫入到緩沖區,就會返回,否則一直阻塞,對於非阻塞模式,是有一個超時時間的,這個由 SO_SNDTIMEO 選項控制,詳細見 socket(7) ,如果當前有空閑位置可以發即當前可寫入,那麼就寫入到緩沖區,知道超時之前寫入多少算多少,然後返回成功寫入的位元組數,如果超時時任何數據都沒寫出去,或者當前就是閉喊戚不可寫入,那麼返回-1 ,並設置errno為 EAGAIN 或 EWOULDBLOCK。
The flags argument is the bitwise OR of zero or more of the following flags.
sendmsg() 使用的 msghdr 結構的定義如下:
對於未連接的套接字 msg_name 指定數據報的目標地址,它指向一個包含地址的緩沖區; msg_namelen 欄位應設置為地址的大小。 對於連接的套接字,這些欄位應分別指定為 NULL 和 0。 這里的未連接指的是數據報協議,連接指的是數據流協議
The msg_iov and msg_iovlen fields specify scatter-gather locations, as for writev(2).
msg_iov是一個buffer數組:
使用 msg_control 和 msg_controllen 成員發送控制信息(輔助數據)。 內核可以處理的每個套接字最大控制緩沖區長度由 /proc/sys/net/core/optmem_max 中的值限制; 見 socket(7) 。 有關在各種套接字域中使用輔助數據的更多信息,請參閱 unix(7) 和 ip(7)。
msg_flags 欄位被忽略。
成功時,返回成功發送的位元組數,這個位元組數並不一定和我們的緩沖區大小相同 。 出錯時,返回 -1,並設置 errno 以指示錯誤。
這些是套接字層生成的一些標准錯誤。 底層協議模塊可能會產生和返回額外的錯誤; 請參閱它們各自的手冊頁。
4.4BSD, SVr4, POSIX.1-2001. These interfaces first appeared in 4.2BSD.
POSIX.1-2001 describes only the MSG_OOB and MSG_EOR flags. POSIX.1-2008 adds a specification of MSG_NOSIGNAL. The MSG_CONFIRM flag is a Linux extension.
根據 POSIX.1-2001,msghdr 結構的 msg_controllen 欄位應該是 socklen_t 類型,而 msg_iovlen 欄位應該是 int 類型,但是 glibc 目前將兩者都視為 size_t。
有關可用於在單個調用中傳輸多個數據報的 Linux 特定系統調用的信息,請參閱 sendmmsg(2)。
Linux may return EPIPE instead of ENOTCONN.
getaddrinfo(3) 中顯示了使用 send() 的示例。
『玖』 send的Linux C 函數
經套接字傳送消息
相關函數
sendto,sendmsg,recv,recvfrom,recvmsg,socket
表頭文件
#include < sys/socket.h >
定義函數
ssize_t send (int s,const void *msg,size_t len,int flags);
參數說明
第一個參數指定發送端套接字描述符;
第二個參數指明一個存放應用程式要發送數據的緩沖區;
第三個參數指明實際要發送的數據的字元數;
第四個參數一般置0。
函數說明
send() 用來將數據由指定的 socket 傳給對方主機。使用 send 時套接字必須已經連接。send 不包含傳送失敗的提示信息,如果檢測到本地錯誤將返回-1。因此,如果send 成功返回,並不必然表示連接另一端的進程接收數據。所保證的僅是當send 成功返回時,數據已經無錯誤地發送到網路上。
對於支持為報文設限的協議,如果單個報文超過協議所支持的最大尺寸,send 失敗並將 errno 設為 EMSGSIZE ;對於位元組流協議,send 會阻塞直到整個數據被傳輸。
flags 參數有如下的選擇:
MSG_DONTROUTE 勿將數據路由出本地網路
MSG_DONTWAIT 允許非阻塞操作(等價於使用O_NONBLOCK)
MSG_EOR 如果協議支持,此為記錄結束
MSG_OOB 如果協議支持,發送帶外數據
MSG_NOSIGNAL 禁止向系統發送異常信息
返回值
成功則返回實際傳送出去的字元數,失敗返回-1,錯誤原因存於errno 中。
錯誤代碼
EBADF 參數 s 非法的 socket 處理代碼。
EFAULT 參數中有一指針指向無法存取的內存空間。
WNOTSOCK 參數 s 為一文件描述詞,非 socket。
EINTR 被信號所中斷。
EAGAIN 此動作會令進程阻斷,但參數 s 的 socket 為不可阻斷的。
ENOBUFS 系統的緩沖內存不足。
EINVAL 傳給系統調用的參數不正確。