服務器編程心得(四)—— 如何將socket設置爲非阻塞模式

1. windows平臺上不管利用socket()函數仍是WSASocket()函數建立的socket都是阻塞模式的:html

SOCKET WSAAPI socket(
  _In_ int af,
  _In_ int type,
  _In_ int protocol
);

SOCKET WSASocket(
  _In_ int                af,
  _In_ int                type,
  _In_ int                protocol,
  _In_ LPWSAPROTOCOL_INFO lpProtocolInfo,
  _In_ GROUP          g,
  _In_ DWORD         dwFlags
);

linux平臺上能夠在利用socket()函數建立socket時指定建立的socket是異步的:linux

int socket(int domain, int type, int protocol);

在type的參數中設置SOCK_NONBLOCK標誌便可,例如:windows

int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

2. 另外,windows和linux平臺上accept()函數返回的socekt也是阻塞的,linux另外提供了一個accept4()函數,能夠直接將返回的socket設置爲非阻塞模式:app

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

只要將accept4()最後一個參數flags設置成SOCK_NONBLOCK便可。dom

3. 除了建立socket時,將socket設置成非阻塞模式,還能夠經過如下API函數來設置:異步

linux平臺上能夠調用fcntl()或者ioctl()函數,實例以下:socket

fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
 
ioctl(sockfd, FIONBIO, 1);  //1:非阻塞 0:阻塞

參考: http://blog.sina.com.cn/s/blog_9373fc760101i72a.html函數

可是網上也有文章說(文章連接:http://blog.csdn.net/haoyu_linux/article/details/44306993),linux下若是調用fcntl()設置socket爲非阻塞模式,不只要設置O_NONBLOCK模式,還須要在接收和發送數據時,須要使用MSG_DONTWAIT標誌,即在recv,recvfrom和send,sendto數據時,將flag設置爲MSG_DONTWAIT。是否有要進行這種雙重設定的必要,筆者以爲沒有這個必要。由於linux man手冊上recv()函數的說明中關於MSG_DONTWAIT說明以下:this

Enables nonblocking operation; if the operation would block, the call fails with the error EAGAIN or EWOULDBLOCK (this can also be enabled using the O_NONBLOCK flag  with the F_SETFL fcntl(2)).spa

經過這段話我以爲要麼經過設置recv()函數的flags標識位爲MSG_DONTWAIT,要麼經過fcntl()函數設置O_NONBLOCK標識,而不是要同時設定。

windows上可調用ioctlsocket函數:

int ioctlsocket(
  _In_    SOCKET s,
  _In_    long   cmd,
  _Inout_ u_long *argp
);

將cmd參數設置爲 FIONBIO,*argp=0即設置成阻塞模式,而*argp非0便可設置成非阻塞模式。可是windows平臺須要注意一個地方,若是你對一個socket調用了WSAAsyncSelect()或WSAEventSelect()函數後,你再調用ioctlsocket()函數將該socket設置爲非阻塞模式,則會失敗,你必須先調用WSAAsyncSelect()經過設置lEvent參數爲0或調用WSAEventSelect()經過設置lNetworkEvents參數爲0來分別禁用WSAAsyncSelect()或WSAEventSelect()。再次調用ioctlsocket()將該socket設置成阻塞模式纔會成功。由於調用WSAAsyncSelect()或WSAEventSelect()函數會自動將socket設置成非阻塞模式。msdn上的原話是:

The WSAAsyncSelect and WSAEventSelect functions automatically set a socket to nonblocking mode. If WSAAsyncSelect or WSAEventSelect has been issued on a socket, then any attempt to use ioctlsocket to set the socket back to blocking mode will fail with WSAEINVAL.

To set the socket back to blocking mode, an application must first disable WSAAsyncSelect by calling WSAAsyncSelect with the lEvent parameter equal to zero, or disable WSAEventSelect by calling WSAEventSelect with the lNetworkEvents parameter equal to zero.

網址:https://msdn.microsoft.com/en-us/library/windows/desktop/ms738573(v=vs.85).aspx

4. 在看實際項目中之前一些前輩留下來的代碼中,經過在一個循環裏面調用fcntl()或者ioctlsocket()函數來socket的非阻塞模式的,代碼以下:

for (;;)
{
#ifdef UNIX
    on=1;
    if (ioctlsocket(id, FIONBIO, (char *)&on) < 0)
#endif
            
#ifdef WIN32
    unsigned long on_windows=1;
    if (ioctlsocket(id, FIONBIO, &on_windows) < 0)
#endif
            
            
#ifdef VOS
    int off=0;
    if (ioctlsocket(id, FIONBIO, (char *)&off) <0)
#endif
    {
        if (GET_LAST_SOCK_ERROR() == EINTR)
            continue;
        RAISE_RUNTIME_ERROR("Can not set FIONBIO for socket");
        closesocket(id);
        return NULL;
    }
    break;
}

是否有必要這樣作,有待考證。

相關文章
相關標籤/搜索