1.1.Модели ввода/вывода в Winsock 2.0Спецификация Winsock 2.0 позволяет использовать следующие основные модели выполнения операций ввода/вывода на сокете:1.блокирующий ввод/вывод, 2.мультиплексирование ввода/вывода с помощью select() на блокирующем или неблокирующем сокете,3.асинхронный неблокирующий ввод/вывод с использованием Windows-сообщений о сетевых событиях –WSAAsyncSelect(),4.неблокирующий ввод/вывод с асинхронными сетевыми событиями –WSAEventSelect(),5.совмещенный ввод/вывод (или ввод/вывод с перекрытием-overlapped I/O),6.порт завершения (completion port).Первый вариант является режимом по умолчанию. Второй -мультиплексирование ввода/вывода с помощью select() -может использоваться на любом сокете, как блокирующем, так и неблокирующем, если он переведен в соответствующий режим с помощью функции ioctlsocket(). Идеи, заложенные в эти модели, восходят к UNIX-сокетам, о которых мы говорили уже достаточно много. Третья модель, которая была подробно рассмотрена выше, принадлежит только Windows в силу ее ориентации на взаимодействие с помощью сообщений.Четвертая модель отходит от фиксации сетевых Windows-сообщений о событиях на сокете и вводит понятие собственно сетевого события, о котором мы и поведем речь.Mодель сетевого I/Oс использованием WSAEventSelect()Эта модель оповещения требует создания для сокета некоторого программного объекта-сетевого события типа WSAEVENTс помощью функции WSACreateEvent():WSAEVENTWSACreateEvent(void);Функция возвращает дескриптор событийного объекта (Event-handle), который далее ассоциируется с сокетом вызовом функции WSAEventSelect():int WSAEventSelect(SOCKET sd,WSAEVENT hEventObject,long lNetworkEvents);ПараметрhEventObject являетсянеобязательным. Он определяет сокетное событие, которое нужно сбросить. Использование этого параметра позволяет обойтись без явного вызова функции WSAResetEvent() для сброса события. Тип WSAEVENT является переопределением типа HANDLE для сетевых API.Параметр, указывающий на одно или несколько сетевых событий, lNetworkEvents, задается битовой маской с помощью тех же констант и тем же способом, что и для WSAAsyncSelect() –FD_READ, FD_ACCEPT и так далее (3.6.3.1), например:// Создаем новый событийный объектNewEvent = WSACreateEvent();// Ассоциируем объект события NewEvent с сетевыми событиями FD_ACCEPT и// FD_CLOSE напрослушивающемсокетеWSAEventSelect( ListenSocket, NewEvent, FD_ACCEPT | FD_CLOSE);WSAEventSelect() возвращает 0, если сетевое событие и ассоциированный с ним сокет соответствуют спецификации, в случае ошибки -SOCKET_ERROR. Объект-событие, создаваемый для наших целей, имеет два состояния и может находиться в двух режимах. Состояния известны как "просигналенное" и не-просигналенное", а режимы известны как "ручной сброс состояния" и "автоматический".Для читателя, знакомого с механизмами и методами синхронизации потоков и процессов в Windows, эти понятия хорошо знакомы.WSACreateEvent() первоначально создает объект-событие как "непросигналенное" с ручным сбросом. Когда на сокете происходит сетевое событие, оно переключает его в "просигналенное" состояние. "Ручное" управление обозначает, что приложение само должно (программно) вернуть его в первоначальное состояние после обработки операции ввода/вывода вызовом функции WSAResetEvent(): BOOLWSAResetEvent(WSAEVENThEvent);Когда приложение заканчивает использование некоторого объекта-события, оно выдает функцию WSACloseEvent() для освобождения ресурсов, с ним связанных:BOOLWSACloseEvent(WSAEVENThEvent);Как и WSAAsyncSelect(), WSAEventSelect() автоматически устанавливает сокет в неблокирующее состояние. Для возврата сокета в нормальное блокирующее состояние применяются функцииioctlsocket()/WSAIoctl(). Возникновение того или иного события на сокете отслеживается с помощью функции WSAWaitForMultipleEvents(). Аналог для функции типа WaitForSingleObject() в сетевом APIотсутствует.Эта функция фактически следит за состоянием дескрипторов событийных объектов и возвращает управление в приложение, если один (или несколько) дескрипторов событийных объектов переходит в "просигналенное состояние" или истек установленный таймаут –в связи с этим очевидна ее "неасинхронность". WSAWaitForMultipleEvents() определенаследующимобразом:DWORD WSAWaitForMultipleEvents(DWORD cEvents,const WSAEVENT FAR * lphEvents,BOOL fWaitAll,DWORD dwTimeout,BOOLfAlertable);Параметр cEvents–определяет количество элементов в массиве (тип WSAEVENT) событийных объектов, а lphEventsесть указатель на этот массив.Примечание:Индексы массива объектовсобытий нумеруются с нуля, потому указание cEvents=0означает массив с только одним "нулевым" элементом.На сегодня максимальное количество этих элементов, допустимое в пределах одного потока (thread) и задаваемое константой WSA_MAXIMUM_WAIT_EVENTS, равно 64. Поэтому если надо работать более чем с 64 объектами, надо создавать дополнительные рабочие потоки. Параметр fWaitAllопределяет, ждать ли все объекты, или "вразброс". Если параметр равен "TRUE", то условием завершения будет "просигналенное" состояниедля всех дескрипторов. В противном случае, любой первый "просигналенный" объект прекратит мониторинг, а возвращаемое значение, вычисляемое как "значение возврата минус WSA_WAIT_EVENT_0" представит собой индекс элемента события в массиве. Обычная практика –это установка этого параметра в FALSE и обслуживание только одного сокетного события. Таймаут dwTimeoutустанавливается в миллисекундах. Если он равен 0, то функция проверяет состояние всех объектов наблюдения и возвращает управление. Если в момент опроса ни один из объектов не поменял своего состояния, функция возвращает признак выхода WSA_WAIT_TIMEOUT. Если dwsTimeoutустановлен в WSA_INFINITE, функция будет бесконечно ждать до возникновения первого события (сравните с поведением функции select() в разделе 2.5.8). Последний параметр fAlertableв "чистой" модели WSAEventSelect() может быть проигнорирован, он предназначен для использования в моделях ввода/вывода с перекрытием и порта завершения, и должен быть установлен в FALSE.Листинг 4.1 демонстрирует простой пример использования рассматриваемой функции:Листинг 4.1 Ожидание сетевых событий в цикле WSAEVENTEventArray[WSA_MAXIMUM_WAIT_EVENTS];intTotal=1, res, i;// Сформировать и занести в массив EventArray дескрипторы объектов-событий...while (1) {res=WSAWaitForMultipleEvents(Total,EventArray,FALSE,WSA_INFINITE,FALSE);if ((res != WSA_WAIT_FAILED) && (res != WSA_WAIT_TIMEOUT)) {// Определяем индекс элемента массива i = res -WSA_WAIT_EVENT_0;// Обрабатываем событие для EventArray[i]...// Сбрасываем событие ("вручную")WSAResetEvent(EventArray[i]);}}В такой схеме есть одна особенность. Если сокетов несколько, а первый (с нулевым индексом) после обслуживания и сброса вдруг снова получил сигнал о событии, и так неоднократно, то он может оставить "на голодном пайке" все остальные событийные объекты, поэтому после обработки надо вызвать WSAWaitForMultipleEvents() индивидуально для каждого элемента массива, указав dwTimeOut равным 0. Если WSAWaitForMultipleEvents() отслеживает возникновение сетевого события на некотором множестве сокетов, то просмотр зафиксированных сетевых событий на данном sdсокете осуществляется функцией WSAEnumNetworkEvents():int WSAEnumNetworkEvents(SOCKET sd,//[in]WSAEVENT hEventObject,//[in]LPWSANETWORKEVENTS lpNetworkEvents//[out]);где hEventObject–дескриптор события, которое должно быть сброшено, а lpNetworkEvents–указатель на структуру для записи событий и возможных ошибок:typedef struct _WSANETWORKEVENTS{long lNetworkEvents;// Сетевоесобытиеint iErrorCode[FD_MAX_EVENTS];// Кодошибки} WSANETWORKEVENTS, FAR* LPWSANETWORKEVENTS;Значения констант вида FD_XXX являются степенями двойки, поэтому их можно объединять операцией orбез потери информации. Поле lNetworkEvents является таким объединением всех констант, задающих события, которые происходили на сокете. Другими словами, если результат операции (lNetworkEvents andFD_XXX) не равен нулю, значит, событие FD_XXX происходило на сокете.С помощью этой функции организуется обработка конкретного события для массива сокетов, например так:WSAEnumNetworkEvents(SocketsArray[index1], EventArray[index2], &info)if(info.lNetworkEvents& FD_READ)//info–структура для записи информации о//произошедших сетевых событиях{// Принимаем и обрабатываем новые данные}а также проверку ошибок –коды ошибок формируются добавлением окончания _BIT к соответствующему событию, например FD_READ_BIT:if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0){printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);break;}Внутренняя системная запись сетевых событий копируется в структуру, на которую ссылается lpNetworkEvents, после чего запись о внутренних сетевых событиях очищается. Если параметр hEventObject не NULL, объект индикации события также сбрасывается. Провайдер сокетов Windows гарантирует, что операции копирования сетевого события, очистка записи о нем и сброс любого связанного объекта события являются автоматическими, и следующее возникновение назначенного сетевого события вновь установит этот объект. В случае возврата этой функцией SOCKET_ERROR, объект события не сбрасывается, и запись о нем не очищается.Модель ввода/вывода с использованием событийных объектов и функции WSAEventSelect() освобождает программиста от необходимости принимать, передавать и обрабатывать как сообщения Windows, так и свои собственные. Вот почему эта модель очень удобна при написании консольных приложений и сервисных программ, исполняющихся в фоновом режиме. Mодель с перекрытием –Overlapped I/O.Следующая модель исполнения операций ввода/вывода –это модель с перекрытием (ее также называют моделью совмещенных операций) –Overlapped I/O. Эта разновидность обмена реализуется на сокетах, которые при формировании были объявлены для исполнения на них совмещенных операций c помощью функции WSASocket() cфлагом WSA_FLAG_OVERLAPPED, а также на сокетах, сформированных обычной функцией socket().Модель I/O с перекрытием является самой производительной из всех досих пор рассмотренных моделей. Она впервые появилась в спецификации WinSock 1.1, но была вначале реализована лишь на Windows NT. Специальных функций для overlappedI/Oв WinSock 1.1 не было и надо было использовать функции ReadFile и WriteFile, в которые вместо дескриптора файла подставлялся дескриптор сокета. Суть этой модели в том, что она позволяет приложению выдать "одновременно" несколько асинхронных запросов на исполнение операций ввода/вывода, используя специальные структуры данных. Затем, по прошествию некоторого времени, приложение может проверить, выполнены ли его запросы и обработать результаты завершившихся операций. Так как провайдер сетевого транспорта Msafd.dll использует сервисы драйвера файловой системы AFD.sys (Ancillary Function Driver, AFD) то существенно, что производится меньшее количество вызовов memcpy() внутри AFD и TCP стека. Если на сокете постоянно «висит» операция перекрытого чтения —то AFD копирует данные сразу из сетевых пакетов в user buffer, связанный с этим чтением. Если же не висит —то AFD приходится использовать свой внутренний буфер, и получается на один вызов memcpy() больше. В настоящее время эта модель доступна на всех современных версиях Windows, за исключением Windows CE.Если в версии WinSock 1.1 эта модель опиралась на обычные функции чтения и записи ReadFile() и WriteFile(), которые были введены в нее по аналогии с функциями read() и write() *nix-систем, то в спецификации WinSock 2.0 I/O с перекрытием реализовано в виде новых функций, таких как WSASend() и WSARecv(). Обратная совместимость с WinSock 1.1 в смысле применения ReadFile() и WriteFile() тем не менее реализована не на всех платформах, поэтому для кроссплатформенных приложений надо использовать только WSASend() и WSARecv(). Итак, использование сокета в модели с перекрытием требует установки параметра WSA_FLAG_OVERLAPPED. После успешного создания сокета и привязки его к локальному адресу возможно выполнение с перекрытием следующих операций:1.WSASend()2.WSASendTo()3.WSARecv()4.WSARecvFrom()5.WSAIoctl()6.WSARecvMsg()7.Accep tEx()8.ConnectEx()9.TransmitFile()10.TransmitPackets()11.DisconnectEx()12.WSANSPIoctl()При этом указание в качестве параметра структуры типа WSAOVERLAPPEDобязательно. Присутствие этой структуры в списке параметров приводит к тому, что после вызова эти функции завершаются немедленно, и что особенно важно, вне зависимости от того, блокирующий это сокет или нет! В принципе, такие операции завершаются с выдачей ошибки SOCKET_ERROR, а функция WSAGetLastError() сообщит о статусе продолжающейся операции -WSA_IO_PENDING. В дальнейшем своем поведении эти перечисленные выше 12 функций полагаются только на "свою" структуру WSAOVERLAPPED, с помощью которой обрабатывается завершение запроса на выполнение операции ввода/вывода. Спустя некоторое время приложение должно вернуться к проверке состояния событийного объекта, связанного со структурой WSAOVERLAPPED, которая является своеобразным связующим звеном между инициированием и завершением соответствующей операции и должна быть уникальна для каждого отдельного запроса. ПримечаниеСтруктура WSAOVERLAPPEDполностью идентична структуре OVERLAPPEDWin32 API. Тем не менее, MSDNутверждает, что поля Internal, InternalHigh, Offset и OffsetHigh в структуре OVERLAPPED используются иначе –т.е. их разрешено использовать приложением.typedef struct WSAOVERLAPPED{ DWORD Internal;DWORD InternalHigh;DWORD Offset;DWORD OffsetHigh;WSAEVENT hEvent;} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;Поля Internal, InternalHigh, Offsetи OffsetHigh структуры используются системой и не должны непосредственно использоваться приложением. Поле hEventиспользуется для ассоциации дескриптора сетевого события типа WSAEVENT с конкретной операцией в случае отсутствия процедуры завершения -lpCompletionRoutineравно NULL.Если lpCompletionRoutine не равно NULL, приложение использует это поле по своему усмотрению.Завершение обработки ввода/вывода с перекрытием может выполняться двояким способом –или приложение ждет оповещения о возникновении сетевого события в event-объекте, или приложение обрабатывает сетевую ситуацию при помощи специальной "процедуры завершения", на которую указывает специальный параметр типа WSAOVERLAPPED_COMPLETION_ROUTINE первых 6-ти указанных выше функций. Этот параметр необязателен и представляет собой указатель на специальную функцию, которая вызывается после завершения нужной операции и обрабатывает ее данные.Кроме того, программа может не дожидаться уведомления о наступлении того или иного события, а проверять состояние запроса ввода-вывода с перекрытием с помощью функции WSAGetOverlappedResult(). Для метода оповещения cпомощью объектов-событий WinSock изменяет состояние данного объекта, ассоциированное со структурой типа WSAOVERLAPPEDв "просигналенное", когда запрос на операцию ввода/вывода наконец завершится. Отслеживание этого момента приложение также производит при помощи уже упоминавшейся функции WSAWaitForMultipleEvents(). Определив, что операция завершилась, надо определить и ее статус –успешное завершение или нет –с помощью функции WSAGetOverlappedResult():BOOL WSAGetOverlappedResult( SOCKET sd,LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags);Параметр lpOverlappedуказывает на структуру типа WSAOVERLAPPED, определенную при инициировании операции. Параметр lpcbTransferуказывает на переменную, в которой фиксируется фактическое количество принятых или посланных байт. Параметр fWait определяет поведение функции –должна ли она ожидать, если операция на момент проверки еще не завершилась, или нет. При fWait=TRUE функция не вернет управление вплоть до завершения операции. При fWait=FALSE и еще продолжающейся операции функция возвращает FALSE с индикацией ошибки WSA_IO_INCOMPLETE. Так как в данном случае функция вызывается для уже "свершившегося" события, этот параметр не имеет последствий. Последний параметр lpdwFlags является указателем на переменную, в которую помещаются результирующие значения флагов, если вызов с перекрытием был выполнен функциями WSARecv() или WSARecvFrom().При успешном завершении операции WSAGetOverlappedResult() возвращает TRUE. Если она возвращает FALSE, то:Или I/O-операция еще выполняется.Или I/O-операция завершилась, но с ошибками.Или статус завершения I/O-операции не может быть определен из-засопутствующих ошибок в параметрах WSAGetOverlappedResult(). Совмещенные операции ввода/вывода разрешены и для многоуровневых сервис-провайдеров на сокетах, сформированных с помощью WSPSocket() сфлагомWSA_FLAG_OVERLAPPED.Второй метод отслеживания запросов на выполнение перекрывающихся операций ввода/вывода -это так называемые "процедуры завершения -сompletion routines". Это callback-функции, на которые ссылаются при формировании запроса с перекрытием. Это функция обратного вызова, и именно ее вызывает система, обнаружившая завершенную операцию, и ее основное назначение состоит в обслуживании запроса с перекрытием. ненулевыми параметрами lpOverlapped и/или lpCompletionRoutine) обмен информации происходит в режиме перекрытия. Когда данные для передачи (приёма) будут восприняты транспортным провайдером (помещены в системный буфер передачи сокета и в случае приёма, помещены в пользовательский буфер), то начнёт выполняться функция завершения или будет установлен объект событие. Если операция не закончится немедленно, то её результаты можно получить или в функции завершения или путём вызова функции WSAGetOverlappedResult(), дождавшись установки объекта события.Таким образом, чтобы работать в этом режиме оповещения, приложение должно использовать структуру типаWSAOVERLAPPEDи процедуру завершения, указанную в соответствующем вызове, как было записано в функции WSASend() чуть раньше. Прототип процедуры завершения выглядит так:voidCALLBACKCompletionRoutine(DWORDdwError,DWORD cbTransferred,LPWSAOVERLAPPED lpOverlapped,DWORD dwFlags);Здесь:dwError-определяет статус завершения в соответствии с параметром lpOverlapped.cbTransferred –сколько байтов было перенесено во время операции.lpOverlapped –тот же самый параметр, представляющий структуруWSAOVERLAPPED, переданную в стартовавший I/O-вызов.dwFlagsсодержит любой флаг, с которым соответствующая операция завершилась.Существенным отличием от обработки с помощью событийного объекта, которая была рассмотрена выше, является отсутствие использования поля событийного дескриптора hEvent. Вторая важная особенность –поток, в котором располагается процедура завершения, должен находиться в состоянии ожидания –alertable state. Это можно проделать с помощью функции WSAWaitForMultipleEvents() –создать "пустой" дескриптор события, ни с чем не связанный, или с помощью функции Windows SleepEx() –она работает точно так же, как и WSAWaitForMultipleEvents(), но не нуждается ни в каком объекте. SleepEx() определена так:DWORDSleepEx(DWORDdwMilliseconds, BOOL bAlertable);Первый параметр определяет время ожидания в миллисекундах. Если dwMilliseconds=INFINITE, SleepEx()будет ждать бесконечно. Если bAlertable=FALSEи произошел вызов callback-функции, процедура не исполняется и функция ждет dwMilliseconds. Если установлено значение TRUE,процедура завершения исполняется, а SleepEx() возвращает признак ожидания завершения I/O -WAIT_IO_COMPLETION.ПримечаниеМодель с перекрытием работает на Win9x, но эмулируется вне ядра, в результате чего ожидаемый эффект ускорения работы не достигается в такой мере как для WinNT, Win2Kили WinXP. Ещё интересный способ ускорения-управление внутренними буферами сокета. Если установить их в 0, то во время перекрытого вызова для передачи данных они будут попадатьпрямо в буфер приложения, минуя копирование во внутренние (это делается с помощью setsockopt() и опций SO_SNDBUF/SO_RCVBUF) -это даст оптимизацию если несколько запросов ввода-вывода отправляются одновременно.Пример кода программы с использованием ввода/вывода с перекрытием приведен в приложении 9.Модель порта завершения Последняя модель ввода/вывода, которую мы рассмотрим, это модель "порта завершения" –completion port. Порт завершения представляет собой специальный механизм в составе ОС, с помощью которого приложение использует объединение (пул) нескольких потоков, предназначенных единственно для цели обработки асинхронных операций ввода/вывода с перекрытием.Приложения, которые вынуждены обрабатывать многочисленные асинхронные запросы (речь идет о сотнях и тысячах одновременно поступающих запросах –например, на поисковых серверах или популярных серверах типа www.microsoft.com), с помощью этого механизма могут обрабатывать I/O-запросы существенно быстрее и эффективнее, чем просто запускать новый поток для обработки поступившего запроса. Эта модель позволяет серверу обслуживать большое количество параллельных соединений с использованием небольшого количества рабочих процессов по сравнению с классической схемой “один поток на одно соединение”.Поддержка этого механизма включена в Windows NT, Windows 2000, Windows XP и Windows Server 2003 и особенно эффективна для мультипроцессорных систем. Так, демонстрационный программный код, который опубликован в MSDN, рассчитан на 16-ти процессорную аппаратную платформу.Для функционирования этой модели необходимо создание специального программного объекта ядра системы, который и был назван "порт завершения". Это осуществляется с помощью функции CreateIoCompletionPort(), которая асссоциирует этот объект с одним или несколькими файловыми (сокетными) дескрипторами (см. ниже пример в разделе 4.4.1.1) и который будет управлять перекрывающимися I/O операциями, используя определенное количество потоков для обслуживания завершенных запросов. Для начала нам необходимо создать программный объект -порт завершения I/O, который будет использоваться, чтобы управлять множественными I/O-запросами для любого количества сокетных дескрипторов. Это выполняется вызовом функции CreateIoCompletionPort(), которая определена как:HANDLECreateIoCompletionPort(HANDLE FileHandle,HANDLE ExistingCompletionPort,DWORD CompletionKey,DWORD NumberOfConcurrentThreads);Прежде чем рассматривать параметры подробно, следует отметить, что эта функция фактически используется для двухразличных целей:1.Чтобы создать объект порта завершения2.Связать дескриптор с портом завершенияКогда Вы первоначально создаете объект порта завершения, интерес представляет единственный параметр -NumberOfConcurrentThreads; первые три параметра не существенны. Параметр NumberOfConcurrentThreads специфичен, потому что он определяет число потоков, которым позволяется выполниться одновременно на порте завершения. По идее, нам нужен только один поток для каждого отдельного процессора, чтобы обслужить порт завершения и избежать переключения контекста потока. Значение для этого параметра равное 0 сообщает системе разрешить иметь столько потоков, сколько процессоров имеется в системе. СледующийвызовсоздаетпортзавершенияI/O:CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0, 0);В результате функция возвратит дескриптор, который используется, чтобы идентифицировать порт завершения, когда на него будет назначен дескриптор сокета.Рабочие потоки и порты завершенияПосле того, как порт завершения успешно создан, Вы можете начинать связывать дескрипторы сокета с объектом. Перед этим –и это важно -Вы должны создать один или большее количество рабочих потоков, чтобы обслужить порт завершения, когда пакеты завершения операции для сокета отправлены в объект порта завершения. Можно задаться вопросом, сколько же потоков должно быть создано, чтобы обслужить порт завершения? Это один из более сложных аспектов модели порта завершения, потому что количество потоков, необходимое для обслуживания запросов Важно ввода/ вывода, зависит от общей концепции проекта вашего приложения. В нашем примере мы не очень заботимся о различении типа операции -т.к. здесь надо просто отправлять и принимать символы. В более "серьёзных" программах использование параметра OperationType в структуре PER_IO_OPERATION_DATA может привести к такому коду для точного понимания, что же происходит с сервером:switch (PerIoData->OperationType){case SRV_NEW_CONNECTION:// это первое подключение:...caseSRV_DATA_SEND:// посылаем данные...case SRV_DATA_RECV:// принимаем данные...case SRV_DISCONNECT:// отсоединение.......}При коректном завершении работы порта завершения главное -не освобождать память со структурой OVERLAPPED, пока выполняется какая-нибудь операция на сокете. Так же надо знать, что закрытие сокета с помощью closesocket() прерывает любые продолжающиеся операции на данном порту завершения. После того как вы закроете все сокеты, необходимо завершить все рабочие потоки порта завершения. Для этого воспользуемся функцией PostQueuedCompletionStatus(), которая отправит потоку пакет, заставляющий прекратить работу:BOOL PostQueuedCompletionStatus(HANDLE CompletionPort, // дескрипторпортазавершенияDWORD dwNumberOfBytesTransferred, // возвратизGetQueuedCompletionStatus()DWORD dwCompletionKey, // возвратизGetQueuedCompletionStatus()LPOVERLAPPED lpOverlapped // возвратизGetQueuedCompletionStatus());Параметр CompletionPort -задаёт порт завершения, а остальные параметры задают значения, которые поток получит из функции GetQueuedCompletionStatus() -те мы можем задать непосредственно тип операции для завершения работы -когда поток получит это значение и интерпретирует его соответвующим образом мы можем освободить какие-то ресурсы, сделать какую-то работу, Etc... Рабочий поток получает эти три параметра GetQueuedCompletionStatus() и он может определить, когда ему необходимо завершить свою работу с помощью специального значения, переданного в одном из этих параметров. Например, можно передавать значение 0 в dwCompletionKey-параметре, который рабочий поток будет интерпретировать как команда завершения работы.После закрытия всех рабочих потоков надо закрыть порт завершения через CloseHandle() и завершить программу.1.1.1.Выбор модели I/O для приложенияСемь моделей ввода/вывода WinSock в окружении оконных и консольных приложений и сервисов, различные требования протоколов верхнего (пользовательского) уровня, однопоточность и многопоточность, однопроцессорные хосты и многопроцессорные –весь этот набор ставит программиста перед очень непростым выбором, какую модель избрать для решения данных конкретных задач. Ниже перечислены наиболее очевидные варианты, рассмотренные нами в том или ином объеме: 1."Обычные"блокирующие сокеты–"родное" состояние любого сокета –блокирующее, и вызов операции на нем не вернет управление, пока она не закончится. 2."Чисто" неблокирующие сокеты–Вызов на таких сокетах возвращает управление немедленно, даже если операция еще будет продолжаться. Хотя принципиально до момента возврата программа может производить некоторые действия, этот метод требует постоянного опроса для определения момента завершения операции –обычно с помощью select(). 3.Мультиплексирование с помощью select(). Функция select() блокирует родительский поток до тех пор, пока на одном из сокетов не произойдет некоторое сетевое событие. Обычная практика –использование select() на неблокирующих сокетах во избежание опроса. 4.Асинхронные сокеты–это тоже неблокирующие сокеты (WSAAsyncSelect()), за исключением того, что не требуется производить опрос –модуль стекового протокола посылает специальное оконное сообщение, когда на сокете произойдет нечто интересующее программу. 5.Объекты-события–Используется WSAEventSelect(), механизм похож на метод мультиплексирование с помощью select(), но более эффективный. Кроме того, не стоит забывать, что select()работает с Беркли-сокетами, а WSAEventSelect() работает в среде WinSock. 6.I/O с перекрытием–Одна из наиболее примечательных особенностей WinSock 2, которая дает возможность обобщить на сокетные операции унифицированный I/O-механизм Win32 API и дает существенный выигрыш в производительности по сравнению с предыдущими моделями. 7.I/OСompletionPort–наибольший эффект от применения этой модели может быть достигнут для серверных приложений на многопроцессорных аппаратных платформах.Следует отметить, что применение любого из упомянутых методов в многопоточной среде может привести к существенным модификациям того или иного избранного механизма I/O, так как потоки существенно влияют на его природу.Конечно же, при выборе в первую очередь следует обратить внимание на операционную систему, для которой пишется приложение, ибо все системы обладают различными сетевыми характеристиками. К примеру, Windows 9x не содержат в своем ядре механизма I/O с перекрытием, и если даже метод работает, то только за счет эмуляции на уровне API, и сокетная операция ReadFile() в Windows 9x будет неудачной. В UNIX механизм, аналогичный I/O с перекрытием, отсутствует. Как было уже отмечено выше, в некоторых UNIX-like системах введено понятие "чистых" асинхронных операций aio_*(), но они не соотносятся с WinSock-идеологией и не так широко распространены. Далее, хотя практически все современные UNIX-like системы поддерживают так называемые "потоки Posix –Posix threads", API для создания многопоточных приложений и техника программирования в UNIX и Windows различны. Из перечисленных методов функция select()для неблокирующих сокетов считается наименее эффективной из-за больших накладных расходов на ее исполнение, и эти расходы растут линейно при увеличении числа обслуживаемых соединений. Вместе с тем она хороша по причине портабельности кода, потому что все системы ее поддерживают.Асинхронная модель с WSAAsyncSelect()также не принадлежит к числу очень эффективных, и она хороша для небольших объемов данных, к тому же она работает только в оконном окружении.Модель с WSAEventSelect() обладает несколько большей эффективностью по сравнению с моделью WSAAsyncSelect(), плюс хорошая совместимость с "безоконными" приложениями. Она рекомендуется для серверов, получающих до 100 запросов на соединение и их обработку. Проблема лишь в том, что можно запустить "в дело" не более 64 объектов (в одном потоке) одновременно, и если будет создано 1024 сокета, это потребует 16–ти потоков!Небольшой трафик –1-100 запросов на соединение -может практически без ущерба обслуживаться любой из этих моделей.Для высокопроизводительных и достаточно загруженных по количеству запросов серверов безусловно рекомендуется модель I/O с перекрытием –ни одна прежде рассмотренная модель не может себя с ней сравнить. Она позволяет справиться с нагрузкой десятков тысяч запросов на соединение (при условии достаточного объема быстродействующей оперативной памяти на сервере), и еще более производительная модель порта завершения.Следующие рекомендации могут быть отнесены к среде исполнения. Например, не рекомендуется программировать блокирующие вызовы в однопоточной среде с графическим интерфейсом (GUI), в этом случае лучше работать с асинхронными сокетами. В клиентских программах "усиленное" использование многопоточности редко оправдано, к тому же надо принимать во внимание вопросы синхронизации и отладки.Многопоточность оправдана для FTP-серверов, длявеб-броузеров, позволяющих закачку файлов в фоновом режиме, для посылки писем в email-клиенте без прерывания работы пользователя и так далее –программа должна применять многопоточность грамотно и уместно. Возможно также применение многопоточных клиентских приложений для задач тестирования серверов.Заблуждение первое: сделав два потока вместо одного, мы повысим производительность в два раза. Это не верно. Во-первых потому что ресурс который мы делим между потоками может просто не дать нам повысить производительность. Например, если сеть имеет ширину канала 100mbs, и один поток использовал из них 60mbs, то понятно, что второй поток сможет использовать только оставшихся 40mbs, но никак не 60mbs. Во-вторых само по себе содержание потоков, довольно расточительно для системы. Само по себе переключение между потоками занимает процессорное sAcceptSdвремя. Кроме того, потоки не работают с максимальной эффективностью, из-за необходимой синхронизации доступа к данным. Заблуждение второе: мы можем создать любое необходимое число потоков. Это не так. Нужно помнить, что создание потока –ресурсоемкая операция. Так, при создании потока за ним сразу закрепляется 1mb памяти под стек. Это значит, что если у вас памяти имеет размер 2ГБ, то вы не сможете создать более 2000 потоков. Размер стека определяется линковщиком, и для VS это значение по умолчанию равно 1 мегабайту. Однако, мы можем изменить размер стека при создании потока. Для этого в С# нужно использовать конструктор Thread(ThreadStart start, int maxStackSize). Здесьвторой параметр задает размер стека для потока. Однако даже в таком случае система выделяет минимум 64КБ под стек (или 256КБ для Vista и Win7), меньший стек сделать нельзя.