Те, кто работает с TCP-сокетами, знают что сокет может работать в блокирующем или неблокирующем (nonblocking) режиме. Windows-сокеты, после создания, находятся в блокирующем режиме, но их можно перевести в неблокирующий функцией ioctlsocket().

При работе над одним проектом у меня появилась задача определить, в каком режиме работает сокет, предоставленный мне DLL-функцией. Т.е. кроме самого сокета, у меня никакой информации не было и приходили они в непредсказуемом режиме.

Под *nix blocking-режим без проблем определяется вызовом функции fcntl(), но под WinSock2 ничего подобного не обнаружилось, и на форумах ничего кроме «Windows does not offer any way to query whether a socket is currently set to blocking or non-blocking» никто не ответил.

Но способ определения все-таки существует:

// GetBlockingMode возвращает: 1 - nonblocking | 0 - blocking | -1 - error | -2 - timeout reseted!

int GetBlockingMode(int Sock)
{
	int iSize, iValOld, iValNew, retgso;
	iSize = sizeof(iValOld);
	retgso = getsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&iValOld, &iSize); // Save current timeout value
	if (retgso == SOCKET_ERROR) return (-1);
	iValNew = 1;
	retgso = setsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&iValNew, iSize); // Set new timeout to 1 ms
	if (retgso == SOCKET_ERROR) return (-1);
	
	// Ok! Try read 0 bytes.
	char buf[1]; // 1 - why not :)
	int retrcv = recv(Sock, buf, 0, MSG_OOB); // try read MSG_OOB
	int werr = WSAGetLastError();
	
	retgso = setsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&iValOld, iSize); // Set timeout to initial value
	if (retgso == SOCKET_ERROR) return (-2);

	if (werr == WSAENOTCONN) return (-1);
	if (werr == WSAEWOULDBLOCK) return 1;
	return 0;
}


Функция принимает номер сокета и возвращает 1 если сокет в nonblocking mode, 0 — blocking, -1 если произошла ошибка определения и -2 если сокет после определения режима остался с маленьким таймаутом.

Вкратце последовательность действий такова:

1. Сохраняем значение таймаута сокета (по умолчанию там 0 — «ждать вечно»).
2. Устанавливаем таймаут в 1 милисекунду.
3. Читаем из сокета 0 (ноль) байт Out of Band данных. Тут нужно пояснение: Если передаются OOB-данные, то функция может врать, но я никогда не сталкивался с OOB с тех пор, как Windows NT4 валилась в синий экран при попытке принять такое (WinNuke).
4. Получаем ошибку, которая произошла в результате чтения.
5. Восстанавливаем старое значение таймаута сокета.
6. Смотрим что за ошибка чтения у нам была: если WSAEWOULDBLOCK — то сокет находится в nonblocking mode, что и требовалось определить.

К недостаткам данного метода, кроме уже упомянутого MSG_OOB, можно отнести задержку определения в 1 миллисекунду и ненулевую вероятность испортить таймаут чтения сокета (хотя нагрузочный тест ни разу такого поведения не выявил).

Комментарии (8)