1. 程式人生 > >伺服器程式設計心得(四)—— 如何將socket設定為非阻塞模式

伺服器程式設計心得(四)—— 如何將socket設定為非阻塞模式

1. windows平臺上無論利用socket()函式還是WSASocket()函式建立的socket都是阻塞模式的:

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是非同步的:

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

在type的引數中設定SOCK_NONBLOCK標誌即可,例如:

int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
2. 另外,windows和linux平臺上accept()函式返回的socekt也是阻塞的,linux另外提供了一個accept4()函式,可以直接將返回的socket設定為非阻塞模式:
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即可。

3. 除了建立socket時,將socket設定成非阻塞模式,還可以通過以下API函式來設定:

linux平臺上可以呼叫fcntl()或者ioctl()函式,例項如下:

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

但是網上也有文章說(文章連結: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說明如下:

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)).

通過這段話我覺得要麼通過設定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.

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;
}

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