Для изменения режима работы сокета с блокирующего на неблокирующий используется функция ioctlsocket в вторым входящим параметром заданным константой FIONBIO, а в качестве третьего параметра передаётся 0(переключить в блокирующий) или 1(переключить в неблокирующий) в зависимости в какой режим требуется переключить сокет, вот пример: ioctlsocket(socket,FIONBIO,Ar0); где первый параметр - socket является переменной типа TSocket, то-есть сокетом режим которого переключается функцией.
Select(0,@FDSet,nil,nil,nil);
При работе с блокируемыми сокетами функции recv(чтение полученных сообщений), connect(сообщает о подключении клиента) и accept(подключение нового клиента к серверу) приводят к прекращению выполнения следующих за ними строк программы до момента, когда произойдёт событие соответствующее назначению функции, только после этого продолжается выполнение программы. Такой подход может быть не уместным, если пишется сервер предназначенный для работы с несколькими клиентами. То есть предположим что к серверу подключилось 2 клиента и программа находится в ожидании получения сообщения от первого клиента, при этом если от второго поступит сообщение на который сервер должен ответить, сделать этого сервер не может пока не получит сообщение от первого или связь с первым не разорвётся(в случаи разрыва при работе с неблокируемыми сокетами recv так же возвращает управление программе).
Для того чтобы предотвратить ситуацию с ожиданием с неблокируемыми сокетами описанную выше, можно как один из вариантов перед непосредственным вызовом функций recv connect или accept проводить проверку о топ прошли или нет на интересующем нас сокете события для обработки которых используются описанные функции. Проверив наличие такого события мы затем может вызвать указанные функции и в таком случаи управление программе должно вернуться сразу. Для проверки наличия таких событий может быть использован следующий функционал:
- select(0,@FDSet1,@FDSet2,@FDSet3,@operationTime); - функция проверяет наличие событий: поступления сообщения от клиентов(сокеты с соответствующим событием записываются в переменную передаваемую вторым параметром), возможности передачи сообщения клиенту(записывается переменную с третьим параметром), а так же ещё какое то событие(переменная передаётся 4-ым параметром), назначение которого я не понял. Пятым параметром передаётся время в течении которого select производит проверку, то есть ожидает наличие хотя бы одного события. Переменная времени является переменной типа TTimeVal, то есть может быть описана так: operationTime: TTimeVal; задать данной переменной время в 1.5 секунды можно так: operationTime.sec:= 1;operationTime.usec:= 500000; функция select возвращает значенеи типа integer, а именно -1 в случаи ошибки, 0 - если нет ни одного из искомых событий или если события есть то вернёт сумму количества сокетов по всем событиям(ниже будет более детальные пример как считается эта сумма и что в ней не учитывается).
- процедура FD_Zero(FDSet1); очищаем переменную списка сокетов, рекомендуется сделать это перед началом использования переменной. Переменная FDSet1 описывается так: var FDSet1: TFDSet;
- процедура FD_Set(socket,FDSet1); - добавляет сокет к переменной FDSet1(то есть FDSet1 в конечном счёте будет содержать все сокеты которые мы в дальнейшем проверить на наличие какого либо события используя функцию select), переменная socket описывается следующим образом var socket: TSocket;
- процедура FD_Clr(socket,FDSet1); - удаляет socket из переменной FDSet1;
- функция FD_IsSet(socket,FDSet1); - проверяет наличие socket в FDSet1.
Детальнее о применении select
Теперь более детально разберём практическое применение применение функции select и заодно более подробно как считается сумма событий когда select возвращает в качестве результата выполнения положительное число и что в эту сумму не входит.
С помощью функции select мы может отслеживать не все группы событий а только те которые нас интересуют. Например нужно определить наличие события получения сообщения на клиентском сокете. Для этого сначала очищаем переменную FDSet1 используя процедуру FD_Zero далее этой переменной передаём наш клиентский сокет используя процедуру FD_Set после чего выполняем select(0,@FDSet1,nil,nil,@operationTime); то есть типы событий, которые не требуется отслеживать в соответствующие им позиции в select передаётся nil. Далее если выполнение select не вернуло ошибку, в этом случаи если на проверяемый сокет не поступило сообщений(в данном случаи select должна была вернуть 0 в рассматриваемом примере) то из переменной FD_Set1 будет удалён искомый сокет и проверка функцией FI_IsSet должна это показать(хотя в данном случаи в этом нет необходимости поскольку выполнение select вернуло 0, а значит никаких событий на искомых сокетах не найдено). Если же выполнение select вернуло значение большее 0(в рассматриваемом примере это может быть только значение 1, поскольку для того чтобы select вернуло большее значение, то проверку нужно было проводить или на нескольких сокетах или по разным типам событий), то соответственно в переменной FD_Set1 из изначально переданных в неё сокетов с помощью функции D_Set останутся только те на которых были получены сообщения(разумеется если они ещё не были прочитаны и находятся в буфере сокета). То есть в рассматриваемом примере переменная FD_Set1 останется без изменений, то есть в ней будет содержаться единственный клиентский сокет который в ней и был и который ранее(до выполнения select) был передан в неё процедурой FD_Set. Обратите внимание на важный момент: если функция select вернула ошибку, то никаких изменений с переменными типа TFDSet она не производила и соответственно все они остались без изменений, а значит по-прежнему содержат все изначально переданные в них сокета. Таким образом применение функции FD_IsSet если выполнение select вернуло ошибку(то-есть -1) бесполезно, поскольку все переменные переданные в select типа TFDSet остались без изменения.
Рассмотрим применение select с целью проверки присоединившихся к серверу клиентов, то есть для оценки может ли быть использован accept так чтобы выполнение программы не заблокировалось. Для анализа этого события переменную типа TFDSet необходимо передать вторым параметром select(то-есть аналогично если бы мы проверяли пришли ли на сокет сообщения которые нужно прочитать). Если серверный сокет слушает порт на подключение новых клиентов и клиентов для которых необходимо выполнить accept(то есть создать сокет для данного подключившегося клиента) нет, то выполнение select вернёт 0, если произошла ошибка выполнение select вернёт -1 и если на сокете есть новые подключившиеся клиенты которых серверный сокет ещё не обработал выполнением accept, то выполнение select вернёт 1, приём даже если на серверном сокете для которого мы выполняем проверку(в данном примере единственном) несколько новых подключившихся клиентов для которых необходимо выполнить accept(например 2 новых клиента), выполнение select всё равно вернёт 1 в рассматриваемом случаи, а вот если бы мы передали вторым параметром в select переменную типа TFDSet содержащую 2 серверных сокета на каждом из которых были бы новые клиенты для которых сервер ещё не выполнил accept(причём не важно на каждом серверных сокетов был бы один клиент ожидающий выполнение appcet от сервера или их было бы несколько, главное чтобы на каждом из свереных клиентов был как минимум один такой клиент) и при этом третий и четвёртый параметры передаваемые в select были бы nil, то выполнение select вернуло бы значение 2.
Теперь выясним как select считает сумму событий, то есть как получается то положительное число возвращаемое select, когда результат(то есть это число) больше 0. Для этого рассмотрим следующий пример: предположим есть серверный TCP сокет который ожидает подключения клиентов. Предположим ситуацию, когда один клиент уже подключился и сервер принял его соединение обработав accept, так же этот клиент уже отправил серверу сообщение, которое сервер ещё не обработал recv. Кроме того к этому же серверу подключилось ещё 2 других клиента, которых сервер ещё не принял, то есть не выполнил accept для них. Предположим переменная серверного сокета называется serverSocket(иммет тип TSocket), а переменная которая хранит сокет единственного обработанного accept сервером клиента - clientSocket(так же типа TSocket). Создадим 2 переменные типа TFDSet, а именно FDSet1 и FDSet2 каждой из которых через процедуру FD_Set передадим serverSocket и clientSocket.