Использование неблокирующих сокетов и обслуживание клиентов одним процессом при помощи функции select().
Пример использования функции.
main(int argc, char *argv[])
struct hostent *host;
struct in_addr addr;
printf(”Official name: %s\n”, host->h_name);
printf(”alias[%d]: %s\n”, i+1, host->h_aliases[i]);
printf(” Adress type=%d\n”, host->h_addrtype);
printf(”Addr[%d]: %s\n ”, i+1, inet_ntoa(addr));
В приведённом примере определяются только два адреса для хоста с заданным в качестве аргумента при запуске программы именем.
Заметим, что данная функция преобразует имя хоста в IP адрес в том порядке байтов, который необходим для его использования в структуре sockaddr_in.
При создании сервера, обслуживающего сразу несколько клиентов существует несколько путей решения данной задачи, например:
1. Использование блокирующих сокетов и обслуживание взаимодействия с каждым клиентом отдельным процессом.
2. Использование неблокирующих сокетов и обслуживание клиентов одним процессом при помощи функции select().
Рассмотрим каждый из предлагаемых путей.
Использование неблокирующих сокетов и обслуживание клиентов одним процессом при помощи функции select().
Вначале, после создания сокета, необходимо перевести его в неблокирующий режим. Это делается с помощью следующих системных вызовов
int s = socket(PF_INET, SOCK_STREAM, 0);
fcntl(s, F_SETFL, O_NONBLOCK); // установка в неблокирующий режим
Неблокирующий режим сокетов характеризуется тем, что при вызове операции чтения (записи) данных на одном конце сокета в отсутствии вызова операции записи (чтения) данных на другом конце сокета, вызов чтения (записи) завершится с кодом выполнения =-1 ошибкой EWOULDBLOCK в переменной errno.
Дальнейшие действия по обслуживанию нескольких клиентов могут быть реализованы следующим образом — сервер в цикле будет опрашивать все созданные сокеты (например функциями recv() и send()), если сокет не готов для приема или передачи данных, то системные вызова будут возвращать код =-1 и значение errno равное EWOULDBLOCK, если же сокет готов к приему или передачи данных то системные вызовы записи или чтения данных в (из) сокет выполняться успешно и далее продолжится по циклу опрос всех сокетов.
Однако, есть белее элегантное решение с использованием функции select(). Данная функция имеет следующий синтаксис и описание
#include
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
FD_SET(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
Функция select() даёт нам возможность одновременной проверки нескольких сокетов, чтобы увидеть, если у них данные, ожидающие recv() или можете ли вы send() данные в сокет без блокирования. Данная функция работает в режиме блокировки, пока либо не произойдут события, связанные с появлением возможности чтения или записи в сокеты, либо не истечет время тайм атута, задаваемое для этого вызова. Аргументы функции select() имеют следующий смысл:
fd_set *readfds, fd_set *writefds, fd_set *exceptfds — это указатели на наборы дескрипторов сокетов, предназначенных для операций чтения, записи и исключительных ситуаций. Если в программе, например, используются только мониторинг сокетов для возможности чтения из них (т.е применения функции recv()), то вместо остальных наборов дескрипторов сокетов необходимо использовать NULL.
int n — это число определяется, как максимальное значение дескриптора, находящегося в наборах readfds, writefds и exceptfds плюс 1. Структура struct timeval *timeout позволяет указать , как долго должен ждать вызов select(), чтобы произошли события в наборах дискрипторов. Структура struct timeval состоит из двух полей: tv_sec — число секунд, и tv_usec — число миллисекунд ( 1000000 миллисекунд = 1 секунда).
Функция select() возвращает число дескрипторов в наборах, готовых к операциям чтения или записи, 0 — если истекло время тайм аута, а готовности дескрипторов нет и -1 в случае ошибки. При этом наборы дескрипторов readfds, writefds и exceptfds модифицируются для указания какие дескрипторы готовы к операциям ввода или вывода.
Макросы, указанные после определения функции выше, выполняют следующие функции:
FD_SET(int fd, fd_set *set); добавляет дескриптор int fd в набор fd_set *set
FD_CLR(int fd, fd_set *set); удаляет дескриптор int fd из набора fd_set *set
FD_ISSET(int fd, fd_set *set); возвращает true если int fd из набора fd_set *set готов к выполненю операциы ввода или вывода.
FD_ZERO(fd_set *set); обнуляет содержимое набора fd_set *set
Теперь приведём пример простейшей программы с использованием функции select(), которая будет просматривать сокеты только на предмет возможности чтения из них данных.
int s1, s2, n, rv;
fd_set readfds;
struct timeval tv;
char buf1[256], buf2[256];
// предположим, что мы связались с обоими серверами в данной точке
//s1 = socket(. );
//s2 = socket(. );
//connect(s1, . ).
//connect(s2, . ).
// очищаем набор дескрипторов
FD_ZERO(&readfds);
// добавляем наши дескрипторы в набор
FD_SET(s1, &readfds);
FD_SET(s2, &readfds);
//так, как мы по тексту программы получили дескриптор s2 вторым, то его значение
// «больше», и мы будем его использовать для задания параметра n в select()
n = s2 + 1;
// задаём время тайм аута 10.5 сек
tv.tv_sec = 10;
tv.tv_usec = 500000;
rv = select(n, &readfds, NULL, NULL, &tv);
if (rv == -1) <
perror(«select»); // ошибка при вызове select()
> else if (rv == 0) <
printf(«Timeout occurred! No data after 10.5 seconds.\n»);
> else <
// один или оба дескриптора имеют данные для использования функции recv()
if (FD_ISSET(s1, &readfds)) <
recv(s1, buf1, sizeof buf1, 0);
>
if (FD_ISSET(s2, &readfds)) <
recv(s1, buf2, sizeof buf2, 0);
>
>
select function (winsock2.h)
The select function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O.
Syntax
Parameters
Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.
An optional pointer to a set of sockets to be checked for readability.
An optional pointer to a set of sockets to be checked for writability.
An optional pointer to a set of sockets to be checked for errors.
The maximum time for select to wait, provided in the form of a TIMEVAL structure. Set the timeout parameter to null for blocking operations.
Return value
The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, WSAGetLastError can be used to retrieve a specific error code.
Error code | Meaning |
---|---|
WSANOTINITIALISED | A successful WSAStartup call must occur before using this function. |
WSAEFAULT | The Windows Sockets implementation was unable to allocate needed resources for its internal operations, or the readfds, writefds, exceptfds, or timeval parameters are not part of the user address space. |
WSAENETDOWN | The network subsystem has failed. |
WSAEINVAL | The time-out value is not valid, or all three descriptor parameters were null. |
WSAEINTR | A blocking Windows Socket 1.1 call was canceled through WSACancelBlockingCall. |
WSAEINPROGRESS | A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function. |
WSAENOTSOCK | One of the descriptor sets contains an entry that is not a socket. |
Remarks
The select function is used to determine the status of one or more sockets. For each socket, the caller can request information on read, write, or error status. The set of sockets for which a given status is requested is indicated by an fd_set structure. The sockets contained within the fd_set structures must be associated with a single service provider. For the purpose of this restriction, sockets are considered to be from the same service provider if the WSAPROTOCOL_INFO structures describing their protocols have the same providerId value. Upon return, the structures are updated to reflect the subset of these sockets that meet the specified condition. The select function returns the number of sockets meeting the conditions. A set of macros is provided for manipulating an fd_set structure. These macros are compatible with those used in the Berkeley software, but the underlying representation is completely different.
The parameter readfds identifies the sockets that are to be checked for readability. If the socket is currently in the listen state, it will be marked as readable if an incoming connection request has been received such that an accept is guaranteed to complete without blocking. For other sockets, readability means that queued data is available for reading such that a call to recv, WSARecv, WSARecvFrom, or recvfrom is guaranteed not to block.
For connection-oriented sockets, readability can also indicate that a request to close the socket has been received from the peer. If the virtual circuit was closed gracefully, and all data was received, then a recv will return immediately with zero bytes read. If the virtual circuit was reset, then a recv will complete immediately with an error code such as WSAECONNRESET. The presence of OOB data will be checked if the socket option SO_OOBINLINE has been enabled (see setsockopt).
The parameter writefds identifies the sockets that are to be checked for writability. If a socket is processing a connect call (nonblocking), a socket is writeable if the connection establishment successfully completes. If the socket is not processing a connect call, writability means a send, sendto, or WSASendto are guaranteed to succeed. However, they can block on a blocking socket if the len parameter exceeds the amount of outgoing system buffer space available. It is not specified how long these guarantees can be assumed to be valid, particularly in a multithreaded environment.
The parameter exceptfds identifies the sockets that are to be checked for the presence of OOB data or any exceptional error conditions.
In summary, a socket will be identified in a particular set when select returns if:
- If listen has been called and a connection is pending, accept will succeed.
- Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
- Connection has been closed/reset/terminated.
writefds:
- If processing a connect call (nonblocking), connection has succeeded.
- Data can be sent.
exceptfds:
- If processing a connect call (nonblocking), connection attempt failed.
- OOB data is available for reading (only if SO_OOBINLINE is disabled).
Four macros are defined in the header file Winsock2.h for manipulating and checking the descriptor sets. The variable FD_SETSIZE determines the maximum number of descriptors in a set. (The default value of FD_SETSIZE is 64, which can be modified by defining FD_SETSIZE to another value before including Winsock2.h.) Internally, socket handles in an fd_set structure are not represented as bit flags as in Berkeley Unix. Their data representation is opaque. Use of these macros will maintain software portability between different socket environments. The macros to manipulate and check fd_set contents are:
- FD_ZERO(*set) — Initializes set to the empty set. A set should always be cleared before using.
- FD_CLR(s, *set) — Removes socket s from set.
- FD_ISSET(s, *set) — Checks to see if s is a member of set and returns TRUE if so.
- FD_SET(s, *set) — Adds socket s to set.
The parameter time-out controls how long the select can take to complete. If time-out is a null pointer, select will block indefinitely until at least one descriptor meets the specified criteria. Otherwise, time-out points to a TIMEVAL structure that specifies the maximum time that select should wait before returning. When select returns, the contents of the TIMEVAL structure are not altered. If TIMEVAL is initialized to <0, 0>, select will return immediately; this is used to poll the state of the selected sockets. If select returns immediately, then the select call is considered nonblocking and the standard assumptions for nonblocking calls apply. For example, the blocking hook will not be called, and Windows Sockets will not yield.
WindowsВ 8.1 and Windows ServerВ 2012В R2: This function is supported for Windows Store apps on WindowsВ 8.1, Windows ServerВ 2012В R2, and later.
Socket. Select(IList, IList, IList, Int32) Метод
Определение
Определяет состояние одного или нескольких сокетов. Determines the status of one or more sockets.
Параметры
IList экземпляров Socket для проверки удобства чтения. An IList of Socket instances to check for readability.
IList экземпляров Socket для проверки удобства ведения записи. An IList of Socket instances to check for writability.
IList экземпляров Socket для проверки ошибок. An IList of Socket instances to check for errors.
Значение времени ожидания в миллисекундах. The time-out value, in microseconds. Значение -1 указывает на бесконечное время ожидания. A -1 value indicates an infinite time-out.
Исключения
Параметр checkRead имеет значение null или является пустым. The checkRead parameter is null or empty.
— и — -and- Параметр checkWrite имеет значение null или является пустым. The checkWrite parameter is null or empty — и — -and- Параметр checkError имеет значение null или является пустым. The checkError parameter is null or empty.
Произошла ошибка при попытке доступа к сокету. An error occurred when attempting to access the socket.
.NET 5.0 и более поздние версии: один или несколько сокетов освобождены. .NET 5.0 and later: One or more sockets are disposed.
Примеры
В следующем примере кода используется Select , чтобы определить, какие сокеты прослушивания имеют запрос на соединение. The following code example uses Select to determine which listening sockets have a connection request.
Комментарии
Select — Это статический метод, который определяет состояние одного или нескольких Socket экземпляров. Select is a static method that determines the status of one or more Socket instances. Перед использованием метода необходимо поместить один или несколько сокетов в IList Select . You must place one or more sockets into an IList before you can use the Select method. Проверьте наличие удобочитаемости, вызвав метод Select с IList checkRead параметром. Check for readability by calling Select with the IList as the checkRead parameter. Чтобы проверить сокеты для записи, используйте checkWrite параметр. To check your sockets for writability, use the checkWrite parameter. Для обнаружения условий ошибок используйте checkError . For detecting error conditions, use checkError . После вызова функции Select IList будут заполнены только теми сокетами, которые соответствуют условиям. After calling Select, the IList will be filled with only those sockets that satisfy the conditions.
В состоянии прослушивания удобочитаемость означает, что вызов Accept будет выполнен без блокировки. If you are in a listening state, readability means that a call to Accept will succeed without blocking. Если вы уже приняли подключение, удобочитаемость означает, что данные доступны для чтения. If you have already accepted the connection, readability means that data is available for reading. В таких случаях все операции получения будут выполняться без блокировки. In these cases, all receive operations will succeed without blocking. Удобочитаемость также может указывать на то, что удаленное соединение завершилось. Socket в этом случае вызов Receive будет немедленно возвращаться с нулевым количеством байт. Readability can also indicate whether the remote Socket has shut down the connection; in that case a call to Receive will return immediately, with zero bytes returned.
Select Возвращает, когда по крайней мере один из нужных сокетов (сокеты в checkRead checkWrite списках, и checkError ) соответствует заданным условиям, или превышено значение параметра в зависимости от того, что microSeconds происходит первым. Select returns when at least one of the sockets of interest (the sockets in the checkRead , checkWrite , and checkError lists) meets its specified criteria, or the microSeconds parameter is exceeded, whichever comes first. Значение параметра microSeconds равно-1 указывает неограниченное время ожидания. Setting microSeconds to -1 specifies an infinite time-out.
Если вы сделаете неблокирующий вызов Connect , записи означает, что соединение установлено успешно. If you make a nonblocking call to Connect, writability means that you have connected successfully. Если соединение уже установлено, записи означает, что все операции отправки будут выполнены без блокировки. If you already have a connection established, writability means that all send operations will succeed without blocking.
Если вы выполнили неблокирующий вызов Connect , checkerror параметр определяет сокеты, которые не были успешно подключены. If you have made a non-blocking call to Connect, the checkerror parameter identifies sockets that have not connected successfully.
Используйте Poll метод, если хотите определить только состояние одного объекта Socket . Use the Poll method if you only want to determine the status of a single Socket.
Этот метод не может обнаружить некоторые виды проблем с подключением, например обрыв сетевого кабеля или некорректное завершение работы удаленного узла. This method cannot detect certain kinds of connection problems, such as a broken network cable, or that the remote host was shut down ungracefully. Необходимо попытаться отправить или получить данные для обнаружения ошибок этих типов. You must attempt to send or receive data to detect these kinds of errors.