轉發: http://aigo.iteye.com/blog/1939118
設置成非阻塞模式:
先用fcntl的F_GETFL獲取flags,用F_SETFL設置flags|O_NONBLOCK;
即:
flags = fcntl(sockfd, F_GETFL, 0); //獲取文件的flags值。
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //設置成非阻塞模式;
同時在接收和發送數據時,需要使用MSG_DONTWAIT標志
即:
在recv,recvfrom和send,sendto數據時,將flag設置為MSG_DONTWAIT。
設置成阻塞模式:
先用fcntl的F_GETFL獲取flags,用F_SETFL設置flags&~O_NONBLOCK;
即:
flags = fcntl(sockfd,F_GETFL,0); //獲取文件的flags值。
fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK); //設置成阻塞模式;
同時在接收和發送數據時,需要使用阻塞標志
即:
在recv,recvfrom和send,sendto數據時,將flag設置為0,默認是阻塞。
在將socket設置成非阻塞模式后,每次的對於sockfd 的操作都是非阻塞的;
非阻塞模式下:
connect
=0 當返回0時,表示立即創建了socket鏈接,
<0 當返回-1時,需要判斷errno是否是EINPROGRESS(表示當前進程正在處理),否則失敗。
例如:下面會有select或epoll監聽fd是否建立鏈接,
select監聽connect是否成功的例子,注意getsockopt驗證,因為三次握手的第三個ACK有可能會丟失,但是客戶端認為鏈接已經建立:
int ret = ::connect(_socket_fd, add.addr(), add.length());
if(ret == 0)
{
//建立鏈接成功
}
else if(ret < 0 && errno == EINPROGRESS) //errno == EINPROGRESS表示正在建立鏈接
{
// 等待連接完成,errno == EINPROGRESS表示正在建立鏈接
fd_set set;
FD_ZERO(&set);
FD_SET(_socket_fd,&set); //相反的是FD_CLR(_sock_fd,&set)
time_t = 10; //(超時時間設置為10毫秒)
struct timeval timeo;
timeo.tv_sec = timeout / 1000;
timeo.tv_usec = (timeout % 1000) * 1000;
int retval = select(_socket_fd + 1, NULL, &set, NULL, &timeo); //事件監聽
if(retval < 0)
{
//建立鏈接錯誤close(_socket_fd)
}
else if(retval == 0) // 超時
{
//超時鏈接沒有建立close(_socket_fd)
}
//將檢測到_socket_fd讀事件或寫時間,並不能說明connect成功
if(FD_ISSET(_socket_fd,&set))
{
int error = 0;
socklen_t len = sizeof(error);
if(getsockopt(_socket_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
{
//建立簡介失敗close(_socket_fd)
}
if(error != 0) // 失敗
{
//建立鏈接失敗close(_socket_fd)
}
else
{
//建立鏈接成功
}
}
}
else
{
//出現錯誤 close(_sock_fd)
}
注意:這里主要是想強調當epoll或select監聽到sockfd上有EPOLL_IN或EPOLL_OUT時,即讀寫事件時,並不能說明鏈接已經建立,如上面的代碼。
/*
int error = 0;
socklen_t ilen = sizeof(error);
ret = getsockopt(fd,SOL_SOCKET,SO_ERROR,&error,&ilen);
if(ret < 0)
{
//說明鏈接建立失敗,close(fd);
}
else if(error != 0 )
{
//說明鏈接建立失敗,close(fd);
}
else
{
//說明鏈接建立成功。即可以向fd上寫數據。
}
*/
recv 和 recvfrom
=0 當返回值為0時,表示對端已經關閉了這個鏈接,我們應該自己關閉這個鏈接,即close(sockfd)。另外因為異步操作會用select或epoll做事件觸發,所以:
1、如果使用select,應該使用FD_CLR(sockfd,fd_set)將sockfd清除掉,不再監聽。
2、如果使用epoll,系統會自己將sockfd清除掉,不再進行監聽。
>0 當返回值大於0 且 小於sizeof(buffer)時,表示數據肯定讀完。(如果等於sizeof(buffer),可能有數據還沒讀,應該繼續讀,不可能有大於)
<0 當返回值小於0,即等於-1時,分情況判斷:
1、如果 errno 為 EAGAINE 或 EWOULDBLOCK
表示暫時無數據可讀,可以繼續讀,或者等待epoll或select的后續通知。(EAGAINE,EWOULDBLOCK產生的
原因:可能是多進程讀同一個sockfd,可能一個進程讀到數據,其他進程就讀取不到數據(類似驚群效應),當然
單個進程也可能出現這種情況。對於這種錯誤,不需用close(sockfd)。可以等待select或epoll的下一次觸發,
繼續讀。)
2、如果 errno 為 EINTR
表示被中斷了,可以繼續讀,或者等待epoll或select后續的通知。
否則,真的是讀取數據失敗。(此時應該close(sockfd))
send和sendto
返回值是實際發送的字符數,因為我們知道要發送的總長度,所以,如果沒有發送完,我們可以繼續發送。
<0 當返回值為 -1 時, 我們需要判斷 errno:
1、如果errno為 EAGAINE 或 EWOULDBLOCK ,表示當前緩沖區寫滿,可以繼續寫,
或者等待epoll或select的后續通知,一旦有緩沖區,就會觸發寫操作,這個也是經常利用的一個特性。
2、如果errno為EINTR ,表示被中斷了,可以繼續寫,或者等待epoll或select的后續通知。
否則真的出錯了,即errno不為EAGAINE或EWOULDBLOCK或EINTR,此時應該close(sockfd)
>=0 >=0且不等於要求發送的長度,應該繼續send,如果等於要求發送的長度,發送完畢。