socket異步通訊-如何設置成非阻塞模式、非阻塞模式下判斷connect成功(失敗)、判斷recv/recvfrom成功(失敗)、判斷send/sendto

原文:
將一個socket 設置成阻塞模式和非阻塞模式,使用fcntl方法,即:

設置成非阻塞模式:異步

先用fcntl的F_GETFL獲取flags,用F_SETFL設置flags|O_NONBLOCK;        socket

即:blog

 

      flags = fcntl(sockfd, F_GETFL, 0);                        //獲取文件的flags值。進程

      fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);   //設置成非阻塞模式;事件

同時在接收和發送數據時,須要使用MSG_DONTWAIT標誌get

即:博客

      在recv,recvfrom和send,sendto數據時,將flag設置爲MSG_DONTWAIT。it

設置成阻塞模式:多進程

 

先用fcntl的F_GETFL獲取flags,用F_SETFL設置flags&~O_NONBLOCK;      class

即:

     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作事件觸發,因此:

       一、若是使用select,應該使用FD_CLR(sockfd,fd_set)將sockfd清除掉,再也不監聽。

       二、若是使用epoll,系統會本身將sockfd清除掉,再也不進行監聽。

       >當返回值大於0 且 小於sizeof(buffer)時,表示數據確定讀完。(若是等於sizeof(buffer),可能有數據還沒讀,應該繼續讀,不可能有大於)

       <0 當返回值小於0,即等於-1時,分狀況判斷:

        一、若是   errno   爲  EAGAINE  或 EWOULDBLOCK                                      

                表示暫時無數據可讀,能夠繼續讀,或者等待epoll或select的後續通知。(EAGAINE,EWOULDBLOCK產生的

         緣由:多是多進程讀同一個sockfd,可能一個進程讀到數據,其餘進程就讀取不到數據(相似驚羣效應),固然

         單個進程也可能出現這種狀況。對於這種錯誤,不需用close(sockfd)。能夠等待select或epoll的下一次觸發,

         繼續讀。)

         二、若是   errno   爲  EINTR

                表示被中斷了,能夠繼續讀,或者等待epoll或select後續的通知。

                不然,真的是讀取數據失敗。(此時應該close(sockfd))

 

send和sendto      

        返回值是實際發送的字符數,由於咱們知道要發送的總長度,因此,若是沒有發送完,咱們能夠繼續發送。

          <0 當返回值爲 -1   時, 咱們須要判斷  errno:

                一、若是errno爲  EAGAINE   或 EWOULDBLOCK ,表示當前緩衝區寫滿,能夠繼續寫,

                      或者等待epoll或select的後續通知,一旦有緩衝區,就會觸發寫操做,這個也是常常利用的一個特性。  

                 二、若是errno爲EINTR  ,表示被中斷了,能夠繼續寫,或者等待epoll或select的後續通知。

                       不然真的出錯了,即errno不爲EAGAINE或EWOULDBLOCK或EINTR,此時應該close(sockfd)

          >=0 >=0且不等於要求發送的長度,應該繼續send,若是等於要求發送的長度,發送完畢。

相關文章
相關標籤/搜索