如何設置linux socket爲非阻塞

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

設置成非阻塞模式:

先用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,如果等於要求發送的長度,發送完畢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章