30250

WinSock или Windows socket

Реферат

Информатика, кибернетика и программирование

Существуют две версии WinSock: WinSock 1.1 поддерживает только протокол TCP IP; WinSock 2. WinSock 1.1 состояла в решении проблемы то цель WinSock 2.

Русский

2013-08-24

275.57 KB

122 чел.

WinSock или Windows socket - это интерфейс прикладного программи-рования (API), созданный для реализации приложений в сети на основе прото-кола TCP/IP. Для работы используется WSOCK32.DLL. Эта библиотека нахо-дится в папке \System32 системного каталога Windows.

Существуют две версии WinSock:

WinSock 1.1 - поддерживает только протокол TCP/IP;

WinSock 2.0 - поддерживает дополнительное программное обеспечение.

WinSock 1.1 дал толчок к развитию World Wide Web и позволил получить доступ в Internet обычному пользователю ПК под Windows. Если цель версии 1.1 состояла в решении проблемы, то цель WinSock 2.0 - сделать сетевую среду лучше, быстрее и надежнее. В WinSock 2.0 добавлена поддержка других транс-портных протоколов и новые функциональные возможности обеспечения на-дежности сетевого обмена информацией. WinSock 2.0 позволяет создавать не-зависящие от транспортных протоколов приложения, работающие с TCP/IP (Transmission Control Protocol/Internet Protocol), UDP (User Datagram Protocol), IPX/SPX (Internetwork Packet Exchange/Sequenced Packet Exchange), NetBEUI (NetBios Extended User Interface). Большая эффективность таких приложений достигается за счет совмещенного ввода/вывода и разделяемых сокетов.

Спецификация WinSock разделяет функции на три типа:

- блокирующие и неблокирующие (функции Беркли);

- информационные (получение информации о наименовании доменов, службах, протоколах Internet);

- инициализации и деинициализации библиотеки.

Блокирующая – это функция, которая останавливает работу программы до своего завершения; неблокирующая – это функция, которая выполняется па-раллельно с программой. Список основных функций, необходимых для созда-ния приложения, приведен в таблицах 1, 2, 3. Все описания функций WinSock даны в формате языка С, а примеры их вызова – на Delphi.

Сетевое программирование с помощью сокетов Windows

Именованные каналы пригодны для организации межпроцессного взаимодействия как в случае процессов, выполняющихся на одной и той же системе, так и в случае процессов, выполняющихся на компьютерах, связанных друг с другом локальной или глобальной сетью. Эти возможности были продемонстрированы на примере клиент-серверной системы, разработанной в главе 11, начиная с программы 11.2.

Однако как именованные каналы, так и почтовые ящики (в отношении которых для простоты мы будем использовать далее общий термин — "именованные каналы", если различия между ними не будут играть существенной роли) обладают тем недостатком, что они не являются промышленным стандартом. Это обстоятельство усложняет перенос программ наподобие тех, которые рассматривались в главе 11, в системы, не принадлежащие семейству Windows, хотя именованные каналы не зависят от протоколов и могут выполняться поверх многих стандартных промышленных протоколов, например TCP/IP.

Возможность взаимодействия с другими системами обеспечивается в Windows поддержкой сокетов (sockets) Windows Sockets — совместимого и почти точного аналога сокетов Berkeley Sockets, де-факто играющих роль промышленного стандарта. В этой главе использование API Windows Sockets (или "Winsock") показано на примере модифицированной клиент-серверной системы из главы 11. Результирующая система способна функционировать в глобальных сетях, использующих протокол TCP/IP, что, например, позволяет серверу принимать запросы от клиентов UNIX или каких-либо других, отличных от Windows систем.

Читатели, знакомые с интерфейсом Berkeley Sockets, при желании могут сразу же перейти непосредственно к рассмотрению примеров, в которых не только используются сокеты, но также вводятся новые возможности сервера и демонстрируются дополнительные методы работы с библиотеками, обеспечивающими безопасную многопоточную поддержку.

Привлекая средства обеспечения взаимодействия между разнородными системами, ориентированные на стандарты, интерфейс Winsock открывает перед программистами возможность доступа к высокоуровневым протоколам и приложениям, таким как ftp, http, RPC и СОМ, которые в совокупности предоставляют богатый набор высокоуровневых моделей, обеспечивающих поддержку межпроцессного сетевого взаимодействия для систем с различной архитектурой.

В данной главе указанная клиент-серверная система используется в качестве механизма демонстрации интерфейса Winsock, и в процессе того, как сервер будет модифицироваться, в него будут добавляться новые интересные возможности. В частности, нами будут впервые использованы точки входа DLL (глава 5) и внутрипроцессные серверы DLL. (Эти новые средства можно было включить уже в первоначальную версию программы в главе 11, однако это отвлекло бы ваше внимание от разработки основной архитектуры системы.) Наконец, дополнительные примеры покажут вам, как создаются безопасные реентерабельные многопоточные библиотеки.

Поскольку интерфейс Winsock должен соответствовать промышленным стандартам, принятые в нем соглашения о правилах присвоения имен и стилях программирования несколько отличаются от тех, с которыми мы сталкивались в процессе работы с описанными ранее функциями Windows. Строго говоря, Winsock API не является частью Win32/64. Кроме того, Winsock предоставляет дополнительные функции, не подчиняющиеся стандартам; эти функции используются лишь в случае крайней необходимости. Среди других преимуществ, обеспечиваемых Winsock, следует отметить улучшенную переносимость результирующих программ на другие системы.

Сокеты Windows

Winsock API разрабатывался как расширение Berkley Sockets API для среды Windows и поэтому поддерживается всеми системами Windows. К преимуществам Winsock можно отнести следующее:

• Перенос уже имеющегося кода, написанного для Berkeley Sockets API, осуществляется непосредственно.

• Системы Windows легко встраиваются в сети, использующие как версию IPv4 протокола TCP/IP, так и постепенно распространяющуюся версию IPv6. Помимо всего остального, версия IPv6 допускает использование более длинных IP-адресов, преодолевая существующий 4-байтовый адресный барьер версии IPv4.

• Сокеты могут использоваться совместно с перекрывающимся вводом/выводом Windows (глава 14), что, помимо всего прочего, обеспечивает возможность масштабирования серверов при увеличении количества активных клиентов.

• Сокеты можно рассматривать как дескрипторы (типа HANDLE) файлов при использовании функций ReadFile и WriteFile и, с некоторыми ограничениями, при использовании других функций, точно так же, как в качестве дескрипторов файлов сокеты применяются в UNIX. Эта возможность оказывается удобной в тех случаях, когда требуется использование асинхронного ввода/вывода и портов завершения ввода/вывода.

• Существуют также дополнительные, непереносимые расширения.

WinSock – это сетевой интерфейс прикладного программирования, реализованный на всех платформах Win32, основной интерфейс доступа к разным базовым сетевым протоколам. Интерфейс унаследовал многое от реализации Berkeley (BSD) Sockets на платформах UNIX. В средах Win32 он стал абсолютно независимым от протокола, особенно с выпуском WinSock 2.

Термин сокеты (sockets) используется для обозначения описателей поставщиков транспорта. В Win32 сокет отличается от описателя файла, а потому представлен отдельным типом — SOCKET. С позиций эталонной модели OSI интерфейс Winsock расположен м/у сеансовым и транспортным уровнями. Под управлением Windows прикладной, представительский и сеансовый уровни, в основном относятся к вашему приложению. Cуществуют значительные отличия реализаций сокетов в UNIX и в Windows, что создает очевидные проблемы. Библиотека WinSock поддерживает два вида сокетов — синхронные (блокируемые) и асинхронные (неблокируемые). Синхронные сокеты задерживают управление на время выполнения операции, а асинхронные возвращают его немедленно, продолжая выполнение в фоновом режиме, и, закончив работу, уведомляют об этом вызывающий код.

Устаревшие ОС Windows 3.x поддерживали только асинхронные сокеты, поскольку в среде с корпоративной многозадачностью захват управления одной задачей «подвешивает» все остальные, включая и саму систему. ОС Windows 9x и NT/2000/XP поддерживают оба вида сокетов, однако в силу того, что синхронные сокеты программируются более просто, чем асинхронные, последние не получили большого распространения. Сокеты семейства протоколов TCP/IP используются для обмена данными между узлами сети Интернет.

Сокеты делятся на два типа — потоковые и дейтаграммные. Потоковые сокеты работают с установлением соединения, обеспечивая надежную идентификацию обоих сторон и гарантируя целостность и успешность доставки данных. Дейтаграмные сокеты работают без установления соединения и не обеспечивают ни идентификации отправителя, ни контроля успешности доставки данных, зато они заметно быстрее потоковых. Выбор того или иного типа сокетов определяется транспортным протоколом, на котором работает сервер, клиент не может по своему желанию установить с дейтаграммным сервером потоковое соединение.

Winsock и модель OSI

Поставщики транспорта из каталога Winsock, перечисленные WSAEnumProtocols, работают на транспортном уровне модели OSI, то есть каждый из них обеспечивает обмен данными. Впрочем, все они относятся к какому-то протоколу, а сетевой протокол работает на сетевом уровне, поскольку обусловливает способ адресации каждого узла в сети. Например, UDP и TCP — это транспорты, хотя оба относятся к протоколу IP. Интерфейс Winsock расположен между сеансовым и транспортным уровнями. Winsock позволяет открывать и закрывать сеанс связи и управлять им для любого данного транспорта. Под управлением Windows три верхних уровня: прикладной, представительский и сеансовый, — в основном относятся к приложению Winsock. Другими словами, приложение Winsock управляет всеми аспектами сеанса связи и при необходимости форматирует данные согласно целям программы.

Сокеты Windows

Целесообразно рассмотреть, как доступные протоколы используют средства Winsock. Этот интерфейс основан на понятии сокета. Сокет — это описатель поставщика транспорта. В Win32 сокет отличается от описателя файла, а потому представлен отдельным типом — SOCKET. Сокет создается одной из двух функций:

// Code 1.06

 SOCKET WSASocket (

int af,

int type,

int protocol,

LPWSAPROTOCOL_INFO lpProtocolInfo,

GROUP g,

DWORD dwFlags

)

SOCKET socket (

int af,

int type,

int protocol

 );

Первый параметр — af, определяет семейство адресов протокола. Например, если необходимо создать UDP- или ТСР-сокет, надо подставить константу AF_ INET, чтобы сослаться на протокол IP. Второй параметр — type, это тип сокета для данного протокола. Он может принимать одно из следующих значений: SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RAW и SOCK_RDM. Третий параметр — protocol, указывает конкретный транспорт, если для данного семейства адресов и типа сокета существует несколько записей. В табл. 1.2 перечислены значения, используемые в полях семейства адресов, типа сокета и протокола для данного сетевого транспорта.

Начальные три параметра для создания сокета подразделены на три уровня. Первый и самый важный — семейство адресов. Он указывает используемый в настоящее время протокол и ограничивает применение второго и третьего параметров. Например, семейство адресов ATM (AF_АТМ) позволяет использовать только простые сокеты (SOCK_RAW). Аналогично, выбор семейства адресов и типа сокета ограничивает выбор протокола.

Впрочем, можно передать в параметре protocol значение 0. В этом случае система выбирает поставщика транспорта, исходя из других двух параметров — af и type. Перечисляя записи каталога для протоколов, следует проверить значение поля dwProviderFlags из структуры WSAPROTOCOL_INFO. Если оно равно PFL_ MATCHES_PROTOCOL_ZERO — это стандартный транспорт, применяемый, если в параметре протокола socket или WSASocket передано значение 0.

Перечислив все протоколы с помощью WSAEnumProtocols, следует передать структуру WSAPROTOCOL_INFO в функцию WSASocket как параметр lpProtocolInfo.

Затем необходимо указать константу FROM_PROTOCOL_INFO во всех трех параметрах (af, type и protocol) — для них будут использоваться значения из переданной структуры WSAPROTOCOL_INFO. Так указывается определенная запись протокола.

Теперь рассмотрим два последних флага из WSASocket. Параметр группы всегда равен 0, так как ни одна из версий Winsock не поддерживает группы сокетов. В параметре dwFlags указывают один или несколько следующих флагов:

WSA_FLAG_OVERLAPPED;

 WSA_FLAG_MULTIPOINT_C_ROOT;

WSA_FLAG_MULTIPOINT_C_LEAF;

WSA_FLAG_MULTIPOINT_D_ROOT;

WSA_FLAG_MULTIPOINT_D_LEAF.

 Первый флаг — WSA_FLAG_OVERLAPPED, указывает, что данный сокет допускает перекрытый ввод-вывод — это один из механизмов связи, предусмотренных в Winsock (см. в следующих главах). Если создается сокет функцией socket, флаг WSA_FLAG_OVERLAPPED задан по умолчанию. Рекомендуется всегда задавать этот флаг при использовании WSASocket. Последние четыре флага относятся к сокетам многоадресного вещания.

Серверные API-функции

Сервер — это процесс, который ожидает подключения клиентов для обслуживания их запросов. Сервер должен прослушивать соединения на стандартном имени. В TCP/IP таким именем является IP-адрес локального интерфейса и номер порта. У каждого протокола своя схема адресации, а потому и свои особенности именования. Первый шаг установления соединения — привязка сокета данного протокола к его стандартному имени функцией bind. Второй — перевод сокета в режим прослушивания функцией listen. И наконец, сервер должен принять соединение клиента функцией accept или WSAAccept.

Целесообразно рассмотреть каждый API-вызов, необходимый для привязки, прослушивания и установления соединения с клиентом. Базовые вызовы, которые клиент и сервер должны сделать для установления канала связи:

Функция bind

После создания сокета определенного протокола следует связать его со стандартным адресом, вызвав функцию bind:

// Code 3.04

int bind(

SOCKET s,

const struct sockaddr FAR * name,

 int namelen

)

Параметр s задает сокет, на котором ожидаются соединения клиентов. Второй параметр с типом struct sockaddr — просто универсальный буфер. Фактически, в этот буфер надо поместить адрес, соответствующий стандартам используемого протокола, а затем при вызове bind привести его к типу struct sockaddr. В заголовочном файле Winsock определен тип SOCKADDR, соответствующий структуре struct sockaddr. Далее в главе этот тип будет использоваться для краткости. Последний параметр задает размер переданной структуры адреса, зависящей от протокола. Например, следующий код иллюстрирует привязку при TCP-соединении:

// Code 3.05

SOCKET s;

 struct sockaddr_in tcpaddr;

int port = 5150;

s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

tcpaddr.sin_family = AF_INET;

tcpaddr.sin_port = htons(port);

tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);

bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr));

Подробнее о структуре sockaddr_in было сказано в разделе, посвященном адресации TCP/IP, предыдущей главы. Там приведен пример создания потокового сокета и последующей настройки структуры адреса TCP/IP для приема соединений клиентов. В данном случае сокет указывает на IP-интерфейс по умолчанию с номером порта 5150. Формально вызов blind связывает сокет с IP-интерфейсом и портом.

При возникновении ошибки функция bind возвращает значение SOCKET_ERROR. Самая распространенная ошибка при вызове bind — WSAEADDRINUSE. В случае использования TCP/IP это означает, что с локальным IP-интерфейсом и номером порта уже связан другой процесс, или они находятся в состоянии TIME_WAIT. При повторном вызове bind для уже связанного сокета возвращается ошибка WSAEFAULT.

^ Функция listen

Теперь нужно перевести сокет в режим прослушивания. Функция bind только ассоциирует сокет с заданным адресом. Для перевода сокета в состояние ожидания входящих соединений используется API-функция listen:

// Code 3.06

int listen(

SOCKET s,

 Int backlog

);

Первый параметр — связанный сокет. Параметр backlog определяет максимальную длину очереди соединений, ожидающих обработки, что важно при запросе нескольких соединений сервера. Пусть значение этого параметра равно 2, тогда при одновременном приеме трех клиентских запросов первые два соединения будут помещены в очередь ожидания, и приложение сможет их обработать. Третий запрос вернет ошибку WSAECONNREFUSED. После того как сервер примет соединение, запрос удаляется из очереди, а другой — занимает его место. Значение backlog зависит от поставщика протокола. Недопустимое значение заменяется ближайшим разрешенным. Стандартного способа получить действительное значение backlog нет.

Ошибки, связанные с listen, довольно просты. Самая частая из них — WSAEINVAL, обычно означает, что перед listen не была вызвана функция bind. Иногда при вызове listen возникает ошибка WSAEADDRINUSE, но чаще она происходит при вызове bind.

^ Функции accept и WSAAccept

Все готово к приему соединений клиентов и можно вызвать функцию accept или WSAAccept. Прототип accept:

// Code 3.07

SOCKET accept(

SOCKET s,

struct sockaddr FAR * addr,

int FAR * addrlen

 );

Параметр s — связанный сокет в состоянии прослушивания. Второй параметр — адрес действительной структуры SOCKADDR_IN, a addrlen — ссылка на длину структуры SOCKADDR_IN. Для сокета другого протокола можно заменить SOCKADDR_IN на структуру SOCKADDR, соответствующую этому протоколу. Вызов accept обслуживает первый находящийся в очереди запрос на соединение. По его завершении структура addr будет содержать сведения об IP-адресе клиента, отправившего запрос, а параметр addrlen — размер структуры.

Кроме того, accept возвращает новый дескриптор сокета, соответствующий принятому клиентскому соединению. Для всех последующих операций с этим клиентом должен применяться новый сокет. Исходный прослушивающий сокет используется для приема других клиентских соединений и продолжает находиться в режиме прослушивания.

В Winsock 2 есть функция WSAAccept, способная устанавливать соединения в зависимости от результата вычисления условия:

// Code 3.08

SOCKET WSAAccept(

SOCKET s,

struct sockaddr FAR * addr,

LPINT addrlen,

LPCONDITIONPROC lpfnCondition,

DWORD dwCallbackData

);

Первые три параметрате же, что и в accept для Winsock 1. Параметр lpfnCondition — указатель на функцию, вызываемую при запросе клиента. Она определяет возможность приема соединения и имеет следующий прототип:

// Code 3.09

 int CALLBACK ConditionFunc(

LPWSABUF lpCallerId,

LPWSABUF lpCallerData,

LPQOS lpSQOS,

LPQOS lpGQOS,

LPWSABUF lpCalleeId,

LPWSABUF lpCalleeData,

GROUP FAR * g,

 DWORD dwCallbackData

);

Передаваемый по значению параметр lpCallerId содержит адрес соединяющегося объекта. Структура WSABUF используется многими функциями Winsock 2 и определена так:

// Code 3.10

 typedef struct _WSABUF

{

u_long len;

char FAR * buf;

 } WSABUF, FAR * LPWSABUF;

В зависимости от ее использования, поле len определяет размер буфера, на который ссылается поле buf, или количество данных в буфере buf.

Для lpCallerId параметр buf указывает на структуру адреса протокола, по которому осуществляется соединение. Чтобы получить корректный доступ к информации, просто приведите указатель buf к соответствующему типу SOCKADDR. При использовании протокола TCP/IP это должна быть структура SOCKADDR_IN, содержащая IP-адрес подключающегося клиента. Большинство сетевых протоколов удаленного доступа поддерживают идентификацию абонента на этапе запроса.

Параметр lpCallerData содержит данные, отправленные клиентом в ходе запроса соединения. Если эти данные не указаны, он равен NULL. Большинство сетевых протоколов, таких как TCP/IP, не используют данные о соединении. Чтобы узнать, поддерживает ли протокол эту возможность, можно обратиться к соответствующей записи в каталоге Winsock путем вызова функции WSAEnumProtocols (см. первую главу).

Следующие два параметра — lpSQOS и lpGQOS, задают уровень качества обслуживания, запрашиваемый клиентом. Оба параметра ссылаются на структуру, содержащую сведения о требованиях пропускной способности для приема и передачи. Если клиент не запрашивает параметры качества обслуживания (quality of service, QoS), то они равны NULL. Разница между ними в том, что lpSQOS используется для единственного соединения, a IpGQOS — для групп сокетов. Группы сокетов не реализованы и не поддерживаются в Winsock 1 и 2. Подробнее о QoS — в последующих главах.

Параметр lpCalleeId — другая структура WSABUF, содержащая локальный адрес, к которому подключен клиент. Поле buf указывает на объект SOCKADDR соответствующего семейства адресов. Эта информация полезна, если сервер запущен на многоадресной машине. Если сервер связан с адресом INADDR_ANY, запросы соединения будут обслуживаться на любом сетевом интерфейсе, а параметр — содержать адрес интерфейса, принявшего соединение.

Параметр lpCalleeData дополняет lpCallerData. Он ссылается на структуру WSABUF, которую сервер может использовать для отправки данных клиенту в ходе установления соединения. Если поставщик услуг поддерживает эту возможность, поле len указывает максимальное число отправляемых байт. В этом случае сервер копирует некоторое, не превышающее это значение, количество байт, в блок buf структуры WSABUF и обновляет поле len, чтобы показать, сколько байт передается. Если сервер не должен возвращать данные о соединении, то перед возвращением условная функция приема соединения присвоит полю len значение 0. Если поставщик не поддерживает передачу данных о соединении, поле len будет равно 0. Большинство протоколов: фактически, все, поддерживаемые платформами Win32 — не поддерживают обмен данными при установлении соединения.

Обработав переданные в условную функцию параметры, сервер должен решить: принимать, отклонять или задержать запрос соединения. Если соединение принимается, условная функция вернет значение СF_АССЕРТ, если отклоняется — CF_REJECT. Если по каким-либо причинам на данный момент решение не может быть принято, возвращается CF_DEFER.

Как только сервер готов обработать запрос, он вызывает функцию WSAAccept. Условная функция выполняется в одном процессе с WSAAccept и должна работать как можно быстрее. В протоколах, поддерживаемых платформами Win32, клиентский запрос задерживается, пока не будет вычислено значение условной функции. В большинстве случаев базовый сетевой стек ко времени вызова условной функции уже может принять соединение. При возвращении значения CF_REJECT стек просто закрывает его (см. последующие главы).

При возникновении ошибки возвращается значение INVALID_SOCKET, чаще всего — WSAEWOULDBLOCK. Оно возникает, если сокет находится в асинхронном или неблокирующем режиме и нет соединения для приема. Если условная функция вернет CF_DEFER, WSAAccept генерирует ошибку WSATRY_AGAIN, если CF_REJECT — WSAECONNREFUSED.

API-функции клиента

Клиентская часть значительно проще и для установления соединения требуется всего три шага: создать сокет функцией socket или WSASocket; разрешить имя сервера (зависит от используемого протокола); инициировать соединение функцией connect или WSAConnect.

Из материалов второй главы известно, как создать сокет и разрешить имя IP-узла, так что единственным оставшимся шагом является установление соединения. Во второй главе также рассматривались способы разрешения имен и для других семейств протоколов.

^ Состояния TCP

Для работы с Winsock не обязательно знать о состояниях TCP, но с их помощью можно лучше понять, что происходит с протоколом при вызовах API-функций Winsock. К тому же, многие программисты сталкиваются с одними и теми же проблемами при закрытии сокетов, состояния TCP при этом представляют наибольший интерес.

Начальное состояние любого сокета — CLOSED. Как только клиент инициирует соединение, серверу отправляется пакет SYN и клиентский сокет переходит в состояние SYN_SENT. Получив пакет SYN, сервер отправляет пакет SYN-and-ACK, а клиент отвечает на него пакетом АСК. С этого момента клиентский сокет переходит в состояние ESTABLISHED. Если сервер не отправляет пакет SYN-ACK, клиент по истечении времени ожидания возвращается в состояние CLOSED.

Если сокет сервера связан и прослушивает локальный интерфейс и порт, то он находится в состоянии LISTEN. При попытке клиента установить соединение сервер получает пакет SYN и отвечает пакетом SYN-ACK. Состояние сокета сервера меняется на SYN_RCVD. Наконец, после отправки клиентом пакета АСК сокет сервера переводится в состояние ESTABLISHED.

Существует два способа закрыть соединение. Если этот процесс начинает приложение, то закрытие называется активным, иначе — пассивным. На рис. 3.2 изображены оба вида закрытия. При активном закрытии соединения приложение отправляет пакет FIN. Если приложение вызывает closesocket или shutdown (со вторым аргументом SD_SEND), оно отправляет узлу пакет FIN, и состояние сокета меняется на FIN_WAIT_1. Обычно узел отвечает пакетом АСК и сокет переходит в состояние FIN_WAIT_2. Если узел тоже закрывает соединение, он отправляет пакет FIN, а компьютер отвечает пакетом АСК и переводит сокет в состояние TIME_WAIT.

Состояние TIME_WAIT также называется состоянием ожидания 2*MSL MSL — максимальное время жизни сегмента (Maximum Segment Lifetime), иными словами, время существования пакета в сети перед его отбрасыванием. У каждого IP-пакета есть поле времени жизни (time-to-live, TTL). Если оно равно 0, значит, пакет можно отбросить. Каждый маршрутизатор, обслуживающий пакет, уменьшает значение TTL на 1 и передает пакет дальше. Перейдя в состояние TIME_WAIT, приложение остается в нем на протяжении двух периодов времени, равных MSL. Это позволяет TCP в случае потери заключительного пакета АСК послать его заново, с последующей отправкой FIN. По истечении 2*MSL сокет переходит в состояние CLOSED.

Результат двух других способов активного закрытия — состояние TIME_WAIT. В предыдущем случае только одна сторона отправляла FIN и получала ответ АСК, а узел оставался свободным для передачи данных до момента своего закрытия. Здесь возможны и два других способа. В первом случае, при одновременном закрытии, компьютер и узел одновременно запрашивают закрытие: компьютер отправляет узлу пакет FIN и получает от него пакет FIN.

Затем в ответ на пакет FIN компьютер отправляет пакет АСК и изменяет состояние сокета на CLOSING. После получения компьютером пакета АСК от узла сокет переходит в состояние TIME_WAIT.

Второй случай активного закрытия является вариацией одновременного закрытия: сокет из состояния FIN_WAIT_1 сразу переходит в состояние TIME_WAIT. Это происходит, если приложение отправляет пакет FIN и тут же после этого получает от узла пакет FIN-ACK. В таком случае узел подтверждает пакет FIN приложения отправкой своего, на которое приложение отвечает пакетом АСК.

Основной смысл состояния TIME_WAIT заключается в том, что пока соединение ожидает истечения 2*MSL, сокетная пара, участвующая в соединении, не может быть использована повторно. Сокетная пара — это комбинация локального и удаленного IP-портов. Некоторые реализации TCP не позволяют повторно использовать любой из портов сокетной пары, находящейся в состоянии TIME_WAIT. В реализации Microsoft этого дефекта нет. При попытке соединения с сокетной парой, находящейся в состоянии TIME_WAIT, произойдет ошибка WSAEADDRINUSE. Одно из решений проблемы (кроме ожидания окончания состояния TIME_WAIT пары сокетов, использующей локальный порт) состоит в использовании параметра сокета SO_REUSEADDR. Более подробно SO_REUSEADDR рассматривается в последующих главах.

Наконец, целесообразно рассмотреть пассивное закрытие. По этому сценарию приложение получает от узла пакет FIN и отвечает пакетом АСК. В этом случае сокет приложения переходит в состояние CLOSE_WAIT. Так как узел закрыл свою сторону, он больше не может отправлять данные, но приложение вправе это делать, пока не закроет свою сторону соединения. Для закрытия своей стороны приложение отправляет пакет FIN, после чего ТСР-сокет приложения переводится в состояние LAST_ACK. После получения от узла пакета АСК сокет приложения возвращается в состояние CLOSED.

^ Функции connect и WSAConnect

Осталось обсудить собственно установление соединения. Оно осуществляется вызовом connect или WSAConnect. Сначала можно рассмотреть версию Winsock 1 этой функции:

// Code 3.11

int connect(

 SOCKET s,

 const struct sockaddr FAR * name,

 int namelen

);

Параметры практически не требуют пояснений: s — действительный ТСР-сокет для установления соединения, name — структура адреса сокета (SOCKADDR_IN) для TCP, описывающая сервер к которому подключаются, namelen — длина переменной пате. Версия Winsock 2 этой функции определена так:

// Code 3.12

int WSAConnect(

SOCKET s,

 const struct sockaddr FAR * name,

int namelen,

LPWSABUF lpCallerData,

LPWSABUF lpCalleeData,

 LPQOS lpSQOS,

LPQOS lpGQOS

);

Первые три параметра такие же, как и в функции connect. Следующие два: lpCallerData и lpCalleeData, — это строковые буферы, используемые для приема и отправки данных в момент установления соединения. Параметр lpCallerData указывает на буфер, содержащий данные, отправляемые клиентом серверу вместе с запросом на соединение; lpCallerData — на буфер с данными, возвращаемыми сервером в ходе установления соединения. Обе переменные являются структурами WSABUF, и для lpCallerData поле len должно указывать длину данных передаваемого буфера buf. В случае lpCalleeData поле len определяет размер буфера buf, куда принимаются данные от сервера. Два последних параметра: lpSQOS и lpGQOS, — ссылаются на структуры QoS, определяющие требования пропускной способности отправки и приема данных устанавливаемого соединения. Параметр lpSQOS указывает требования к сокету s, a lpGQOS — к группе сокетов. На данный момент группы сокетов в полной мере не поддерживаются. Нулевое значение lpSQOS означает, что приложение не предъявляет требований к качеству обслуживания.

Если на компьютере, к которому выполнено подключение, не запущен процесс, прослушивающий данный порт, функция connect вернет ошибку WSAECONNREFUSED. Другая ошибка — WSAETIMEDOUT, происходит, когда вызываемый адресат недоступен, например, из-за отказа коммуникационного оборудования на пути к узлу или отсутствия узла в сети.

Передача данных

В сетевом программировании самое главное — уметь отправлять и принимать данные. Для пересылки данных по сокету используются функции send и WSASend. Аналогично, для приема данных существуют функции recv и WSARecv.

Все буферы, используемые при отправке и приеме данных, состоят из элементов типа char. To есть эти функции не предназначены для работы с кодировкой UNICODE. Это особенно важно для Windows СЕ, так как она использует UNICODE по умолчанию. Отправить строку символов UNICODE можно двумя способами: в исходном виде или привести к типу char. Нюанс в том, что при указании количества отправляемых или принимаемых символов результат функции, определяющей длину строки, нужно умножить на 2, так как каждый UNICODE-символ занимает 2 байта строкового массива. Другой способ: сначала перевести строку из UNICODE в ASCII функцией WideCharToMultiByte.

Все функции приема и отправки данных при возникновении ошибки возвращают код SOCKET_ERROR. Для получения более подробной информации об ошибке можно вызвать функцию WSAGetLastError. Самые распространенные ошибки — WSAECONNABORTED и WSAECONNRESET. Обе возникают при закрытии соединения: либо по истечении времени ожидания, либо при закрытии соединения партнерским узлом. Еще одна типичная ошибка — WSAEWOULDBLOCK, обычно происходит при использовании неблокирующих или асинхронных сокетов. По существу, она означает, что функция не может быть выполнена в данный момент. В следующейглаве будут описаны разные методы ввода-вывода Winsock, которые помогут избежать этих ошибок.

^ Функции send и WSASend

API-функция send для отправки данных по сокету определена так:

// Code 3.13

int send(

SOCKET s,

const char FAR * buf,

int len,

 int flags

);

Параметр s определяет сокет для отправки данных. Второй параметр — buf, указывает на символьный буфер, содержащий данные для отправки. Третий — len, задает число отправляемых из буфера символов. И последний параметр — flags, может принимать значения 0, MSG_DONTROUTE, MSG_OOB, или результат логического ИЛИ над любыми из этих параметров. При указании флага MSG_DONTROUTE транспорт не будет маршрутизировать отправляемые пакеты. Обработка этого запроса остается на усмотрение базового протокола (например, если транспорт не поддерживает этот параметр, запрос игнорируется). Флаг MSG_OOB указывает, что данные должны быть отправлены вне полосы (out of band), то есть срочно.

При успешном выполнении функция send вернет количество переданных байт, иначе — ошибку SOCKET_ERROR. Одна из типичных ошибок — WSAECONNABORTED, происходит при разрыве виртуального соединения из-за ошибки протокола или истечения времени ожидания. В этом случае сокет должен быть закрыт, так как он больше не может использоваться. Ошибка WSAECONNRESET происходит, если приложение на удаленном узле, выполнив аппаратное закрытие, сбрасывает виртуальное соединение, или неожиданно завершается, или происходит перезагрузка удаленного узла. В этой ситуации сокет также должен быть закрыт. Еще одна ошибка — WSAETIMEDOUT, часто происходит при обрыве соединения по причине сбоев сети или отказа удаленной системы без предупреждения.

Функция Winsock версии 2 WSASend — аналог send, определена так:

// Code 3.14

int WSASend(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesSent,

DWORD dwFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE

 );

Сокет является действительным описателем сеанса соединения. Второй параметр указывает на структуру WSABUF или на массив этих структур. Третий — определяет число передаваемых структур WSABUF. Структура WSABUF включает сам символьный буфер и его длину. Может возникнуть вопрос: зачем нужно отправлять более одного буфера за раз? Это называется комплексным вводом-выводом (scatter-gather I/O). Подробней он будет обсуждаться далее, сейчас можно отметить, что при использовании нескольких буферов для отправки данных по сокету соединения массив буферов отправляется, начиная с первой и заканчивая последней структурой WSABUF.

Параметр lpNumberOfBytesSent — указатель на тип DWORD, который после вызова WSASend содержит общее число переданных байт. Параметр флагов dwFlags такой же, что и в функции send. Последние два указателя — 1рOverlapped и lpCompletionROUTINE используются для перекрытого ввода-вывода (overlapped I/O) — одной из моделей асинхронного ввода-вывода, поддерживаемых Winsock (см. также следующую главу).

WSASend присваивает параметру lpNumberOfBytesSent количество записанных байт. При успешном выполнении функция возвращает 0, иначе — SOCKET_ERROR. Ошибки те же, что и у функции send.

^ Функция WSASendDisconnect

Это специализированная функция используется редко. Она определена так:

// Code 3.15

int WSASendDisconnect (

SOCKET s,

LPWSABUF lpOUT boundDisconnectData

)

^ Срочные данные

Если приложению требуется отправить через потоковый сокет информацию более высокого приоритета, оно может обозначить эти сведения как срочные данные (out-of-band, OOB). Приложение с другой стороны соединения получает и обрабатывает ООВ-данные через отдельный логический канал, концептуально независимый от потока данных.

В TCP передача ООВ-данных реализована путем добавления 1-битового маркера (называемого URG) и 16-битного указателя в заголовке сегмента TCP, которые позволяют выделить важные байты в основном трафике. На данный момент для TCP существуют два способа выделения срочных данных. В RFC 793, описывающем TCP и концепцию срочных данных, говорится, что указатель срочности в заголовке TCP является положительным смещением байта, следующего за байтом срочных данных. Однако в RFC 1122 это смещение трактуется, как указатель на сам байт срочности.

В спецификации Winsock под термином ООВ понимают как независимые от протокола ООВ-данные, так и реализацию механизма передачи срочных данных в TCP. Для проверки, есть ли в очереди срочные данные, вызывается функция ioctlsocket с параметром SIOCATMARK. Подробнее об этой функции — в последующих главах.

В Winsock предусмотрено несколько способов передачи срочных данных. Можно встроить их в обычный поток, либо, отключив эту возможность, вызвать отдельную функцию, возвращающую только срочные данные. Параметр SO_OOBINLINE управляет поведением ООВ-данных (он подробно обсуждается в последующих главах).

В ряде случаев срочные данные используют программы Telnet и Rlogin. Если не планируется писать собственные версии этих программ, следует избегать применения срочных данных — они не стандартизированы и могут иметь другие реализации на отличных от Win32 платформах. Если нужно время от времени передавать срочно какую-то информацию, можно создать отдельный управляющий сокет для срочных данных, а основное соединение предоставить для обычной передачи данных.

Функция WSASendDisconnect начинает процесс закрытия сокета и отправляет соответствующие данные. Она доступна только для протоколов, поддерживающих постепенное закрытие и передачу данных при его осуществлении. Ни один из существующих поставщиков транспорта на данный момент не поддерживает передачу данных о закрытии соединения. Функция WSASendDisconnect действует аналогично shutdown с параметром SD_SEND, но также отправляет данные, содержащиеся в параметре boundDisconnectData. После ее вызова отправлять данные через сокет невозможно. В случае неудачного завершения WSASendDisconnect возвращает значение SOCKET_ERROR. Ошибки, встречающиеся при работе функции, аналогичны ошибкам send.

^ Функции recv и WSARecv

Функция recv — основной инструмент приема данных по сокету. Она определена так:

// Code 3.16

int recv(

 SOCKET s,

 char FAR * buf,

int len,

 int flags

);

Параметр s определяет сокет для приема данных. Второй параметр — buf, является символьным буфером и предназначен для полученных данных, a len указывает число принимаемых байт или размер буфера buf. Последний параметр — flags, может принимать значения 0, MSG_PEEK, MSG_OOB или результат логического ИЛИ над любыми из этих параметров. Разумеется, 0 означает отсутствие особых действий. Флаг MSG_PEEK указывает, что доступные данные должны копироваться в принимающий буфер и при этом оставаться в системном буфере. По завершении функция также возвращает количество ожидающих байт.

Считывать сообщения таким образом не рекомендуется. Мало того, что из-за двух системных вызовов (одного — для считывания данных, и другого, без флага MSG_PEEK — для удаления данных), снижается производительность. В ряде случаев этот способ просто не надежен. Объем возвращаемых данных может не соответствовать их суммарному доступному количеству. К тому же, сохраняя данные в системных буферах, система оставляет все меньше памяти для размещения входящих данных. В результате уменьшается размер окна TCP для всех отправителей, что не позволяет приложению достичь максимальной производительности. Лучше всего скопировать все данные в собственный буфер и обрабатывать их там. Флаг MSG_OOB уже обсуждался ранее при рассмотрении отправки данных.

Использование recv в сокетах, ориентированных на передачу сообщений или дейтаграмм, имеет несколько особенностей. Если при вызове recv размер ожидающих обработки данных больше предоставляемого буфера, то после его полного заполнения возникает ошибка WSAEMSGSIZE. Ошибка превышения размера сообщения происходит только при использовании протоколов, ориентированных на передачу сообщений. Потоковые протоколы буферизируют поступающие данные и при запросе приложением предоставляют их в полном объеме, даже если количество ожидающих обработки данных больше размера буфера. Таким образом, ошибка WSAEMSGSIZE не может произойти при работе с потоковыми протоколами.

Функция WSARecv обладает дополнительными по сравнению с recv возможностями: поддерживает перекрытый ввод-вывод и фрагментарные дейтаграммные уведомления.

// Code 3.17

int WSARecv(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPOWORD lpNumberOfBytesRecvd,

LPDWORD lpFlags,

LPWSAOVERLAPPED lpOveflapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE

 );

Параметр s — сокет соединения. Второй и третий параметры определяют буферы для приема данных. Указатель lpBuffers ссылается на массив структур WSABUF, a dwBufferCount — определяет количество таких структур в массиве. Параметр lpNumberOfBytesReceived в случае немедленного завершения операции получения данных указывает на количество принятых этим вызовом байт. Параметр lpFlags может принимать значения MSG_PEEK, MSG_OOB, MSG_PARTIAL или результат логического ИЛИ над любыми из этих параметров.

У флага MSG_PARTIAL в зависимости от способа использования могут быть разные значения и смысл. Для протоколов, ориентированных на передачу сообщений, этот флаг задается после вызова WSARecv (если все сообщение не может быть возвращено из-за нехватки места в буфере). В этом случае каждый последующий вызов WSARecv задает флаг MSG_PARTIAL, пока сообщение не будет прочитано целиком. Если этот флаг передается как входной параметр, операция приема данных должна завершиться, как только данные будут доступны, даже если это только часть сообщения. Флаг MSG_PARTIAL используется только с протоколами, ориентированными на передачу сообщений. Запись каждого протокола в каталоге Winsock содержит флаг, указывающий на поддержку этой возможности (см. также последующие главы). Параметры lpOverlapped и lpCompletionROUTINE применяются в операциях перекрытого ввода-вывода (обсуждаются в следующей главе).

^ Функция WSARecvDisconnect

Эта функция обратна WSASendDisconnect и определена так:

// Code 3.18

int WSARecvDisconnect(

SOCKET s,

 LPWSABUF lpInboundDisconnectData

);

Как и у WSASendDisconnect, ее параметрами являются описатель сокета соединения и действительная структура WSABUF для приема данных. Функция принимает только данные о закрытии соединения, отправленные с другой стороны функцией WSASendDisconnect, ее нельзя использовать для приема обычных данных. К тому же, сразу после принятия данных она прекращает прием с удаленной стороны, что эквивалентно вызову shutdown с параметром SD_RECV.

^ Функция WSARecvEx

Эта функция — специальное расширение Microsoft для Winsock 1. Она идентична recv во всем, кроме того, что параметр flags передается по ссылке. Это позволяет базовому поставщику задавать флаг MSG_PARTLAL.

// Code 3.19

int PASCAL FAR WSARecvEx(

 SOCKET s,

 char FAR * buf,

int len,

 int * flags

);

Если полученные данные не составляют полного сообщения, в параметре flags возвращается флаг MSG_PARTIAL. Он используется только с протоколами, ориентированными на передачу сообщений. Когда при принятии неполного сообщения флаг MSG_PARTIAL передается как часть параметра flags, функция завершается немедленно, вернув принятые данные. Если в буфере не хватает места, чтобы принять сообщение целиком, WSARecvEx вернет ошибку WSAEMSGSIZE, а оставшиеся данные будут отброшены. Есть разница между флагом MSG_PARTIAL и ошибкой WSAEMSGSIZE: в случае ошибки сообщение поступило целиком, однако соответствующий буфер слишком мал для его приема. Флаги MSG_PEEK и MSG_OOB также можно использовать в WSARecvEx.

Завершение сеанса

По окончании работы с сокетом необходимо закрыть соединение и освободить все ресурсы, связанные с описателем сокета, вызвав функцию dosesocket. Ее неправильное использование может привести к потере данных, поэтому перед вызовом dosesocket сеанс нужно корректно завершить функцией shutdown.

^ Функция shutdown

Правильно написанное приложение уведомляет получателя об окончании отправки данных. Так же должен поступить и узел. Такое поведение называется корректным завершением сеанса и осуществляется с помощью функции shutdoum:

// Code 3.23

int shutdown(

SOCKET s,

int how

);

Параметр bow может принимать значения SD_RECEIVE, SD_SEND или SD_ BOTH. Значение SD_RECEIVE запрещает все последующие вызовы любых функций приема данных, на протоколы нижнего уровня это не действует. Если в очереди ТСР-сокета есть данные, либо они поступают позже, то соединение сбрасывается. UDP-сокеты в аналогиной ситуации продолжают принимать данные и ставить их в очередь. SD_SEND запрещает все последующие вызовы функций отправки данных. В случае ТСР-сокетов после подтверждения получателем приема всех отправленных данных передается пакет FIN. Наконец, SD_BOTH запрещает как прием, так и отправлку.

^ Функция closesocket

Эта функция закрывает сокет. Она определена так:

// Code 3.24

 int closesocket(SOCKET s);

Вызов closesocket освобождает дескриптор сокета, и все дальнейшие операции с сокетом закончатся ошибкой WSAENOTSOCK. Если не существует других ссылок на сокет, все связанные с дескриптором ресурсы будут освобождены, включая данные в очереди.

Ожидающие асинхронные вызовы, исходящие от любого потока данного процесса, отменяются без уведомления. Ожидающие операции перекрытого ввода-вывода также аннулируются. Все выполняющиеся события, процедура и порт завершения, связанные с перекрытым вводом-выводом, завершатся ошибкой WSA_OPERATION_ABORTED. (Асинхронные и неблокирующие модели ввода-вывода более подробно обсуждаются в следующей главе.) Другой фактор, влияющий на поведение функции closesocket, — значение параметра сокета SO_LINGER (его полное описание — в последующих главах).

Протоколы, не требующие соединения

Принцип действия таких протоколов иной, так как в них используются другие методы отправки и приема данных. Обсудим сначала получателя (или сервер), потому что не требующий соединения приемник мало отличается от серверов, требующих соединения.

Приемник

Процесс получения данных на сокете, не требующем соединения, прост. Сначала создают сокет функцией socket или WSASocket. Затем выполняют привязку сокета к интерфейсу, на котором будут принимать данные, функцией bind (как и в случае протоколов, ориентированных на сеансы). Разница в том, что нельзя вызвать listen или accept: вместо этого нужно просто ожидать приема входящих данных. Поскольку в этом случае соединения нет, принимающий сокет может получать дейтаграммы от любой машины в сети. Простейшая функция приема — recvform.

// Code 3.28

int recvfrom(

SOCKET s,

char FAR* buf,

int len,

int flags,

struct sockaddr FAR * from,

 int FAR * fromlen

);

Первые четыре параметра такие же, как и для функции recv, включают допустимые значения для flags-. MSG_OOB и MSG_PEEK. Параметр from — структура SOCKADDR для данного протокола слушающего сокета, на размер структуры адреса ссылается fromlen. После возврата вызова структура SOCKADDR будет содержать адрес рабочей станции, которая отправляет данные.

В Winsock 2 применяется другая версия recvform — WSARecvForm:

// Code 3.29

int WSARecvFrom(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesRecvd,

LPDWORD lpFlags,

struct sockaddr FAR * lpFrom,

LPINT lpFromlen,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE

 );

Разница между версиями — в использовании структуры WSABUF для получения данных. Можно предоставить один или несколько буферов WSABUF, указав их количество в divBufferCount — в этом случае возможен комплексный ввод-вывод. Суммарное количество считанных байт передается в lpNumberOfBytesRecvd. При вызове функции WSARecvFrom, lpFlags может принимать следующие значения: 0 (при отсутствии параметров), MSG_OOB, MSG_PEEK или MSG_PARTIAL. Данные флаги можно комбинировать логической операцией ИЛИ. Если при вызове функции задан флаг MSG_PARTlAL, поставщик перешлет данные даже в случае приема лишь части сообщения. По возвращении флаг задается в MSG_PARTIAL, только если сообщение принято частично. По возвращении WSARecvFrom присвоит параметру lpFrom (указатель на структуру SOCKADDR) адрес компьютера-отправителя. Опять же lpFromLen указывает на размер структуры SACKADDR, однако в данной функции он является указателем на DWORD. Два последних параметра — lpOverlapped и lpCompletionROUTINE, используются для перекрытого ввода-вывода (см. следующую главу).

Другой способ приема (отправки) данных в сокетах, не требующих соединения, — установление соединения (хоть это и звучит странно). После создания сокета можно вызвать connect или WSAConnect, присвоив параметру SOCKADDR адрес удаленного компьютера, с которым необходимо связаться. Фактически никакого соединения не происходит. Адрес сокета, переданный в функцию соединения, ассоциируется с сокетом, чтобы было можно использовать функции recv и WSARecv вместо recvfrom или WSARecvFrom (поскольку источник данных известен). Если приложению нужно одновременно связываться лишь с одной конечной точкой, можно задействовать возможность подключить сокет дейтаграмм.

Отправитель

Есть два способа отправки данных через сокет, не требующий соединения. Первый и самый простой — создать сокет и вызвать функцию sendto или WSASendTo. Целесообразно рассмотреть сначала функцию sendto:

// Code 3.30

int sendto(

SOCKET s,

 const char FAR * buf,

int len,

int flags,

const struct sockaddr FAR * to,

 int tolen

);

Параметры этой функции такие же, как и у recvfrom, за исключением buf— буфера данных для отправки, и len — показывающего, сколько байт отправлять. Параметр to — указатель на структуру SOCKADDR с адресом принимающей рабочей станции.

Также можно использовать функцию WSASendTo из Winsock 2:

// Code 3.31

int WSASendTo(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesSent,

DWORD dwFlags,

const struct sockaddr FAR * lpTo,

int iToLen,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION ROUTINE lpCompletionROUTINE

 );

Снова функция WSASendTo аналогична своей предшественнице. Она принимает указатель на одну или несколько структур WSABUF с данными для отправки получателю в виде параметра lpBuffers, a divBufferCount задает количество структур. Для комплексного ввода-вывода можно отправить несколько структур WSABUF. Перед выходом WSASendTo присваивает четвертому параметру — lpNumberOfBytesSent, количество реально отправленных получателю байт. Параметр lpTo — структура SOCKADDR для данного протокола с адресом приемника. Параметр iToLen — длина структуры SOCKADDR. Два последних параметра — lpOverlapped и lpCompletionROUTINE, применяются для перекрытого ввода-вывода (см. также следующую главу).

Как и при получении данных, сокет, не требующий соединения, можно подключать к адресу конечной точки и отправлять данные функциями send и WSASend. После создания этой привязки использовать для обмена данными функции sendto или WSASendTo с другим адресом нельзя — будет выдана ошибка WSAEISCONN. Отменить привязку сокета можно, лишь вызвав функцию closesocket с описателем этого сокета, после чего следует создать новый сокет.

Модель ввода - вывода в свою очередь определяет как приложение будет обрабатывать операции ввода - вывода для определённого сокета.

Winsock предостовляет два режима для сокетов: блокирующий и неблокирующий, а также несколько интересных моделей ввода - вывода которые помогают приложениям в управлении оерациями ввода - вывода нескольких сокетов одновременно асинхронным способом: блокирование, select, WSAAsyncSelect, WSAEventSelect, перекрытый ввод - вывод (overlapped I/O), и порт завершения (completion port). Все Windows платформы предоставляют блокирующий и неблокирующий режим работы для сокетов. И всеже не все модели ввода - вывода доступны на всех платформах. Следующая таблица показывает доступность моделей на разных Windows платформах.

Спецификация Winsock 2.0 позволяет использовать следующие основные модели выполнения операций ввода/вывода на сокете:

блокирующий ввод/вывод,

мультиплексирование ввода/вывода с помощью select() на блокирующем или неблокирующем сокете,

асинхронный неблокирующий ввод/вывод с использованием Windows-сообщений о сетевых событиях – WSAAsyncSelect(),

неблокирующий ввод/вывод с асинхронными сетевыми событиями – WSAEventSelect(),

совмещенный ввод/вывод (или ввод/вывод с перекрытием - overlapped I/O),

порт завершения (completion port).

Режимы сокетов

Как мы уже упомянали, Windows сокеты могут выполнять операции ввода - вывода в двух режимах: блокирующий и неблокирующий. В блокирующем режиме вызовы Winsock функций которые выполняют операции ввода - вывода, таких как send и recv - ждут пока операция завершится прежде чем отдать управление приложению. В неблокирующем режиме Winsock функции отдают управление приложению сразу. Приложения которые работают на Windows CE и Windows 95 (Winsock 1) платформах, которые потдерживают только некоторые модели ввода - вывода, вынуждены выполнять оперделенные дейвсвия с блокирующими и неблокирующими сокетами для коректной отработки разных ситуаций.

1.1. Блокирующий режим

Блокирующие сокеты создают некоторые неудобства, потомуч то вызов любой из Winsock API функций блокируют на некоторое время. Большинство Winsock приложений следуют модели "производитель - потребитель" в которой приложение считывает либо записывает определенное количество байт и выполняет их обработку. Следующий отрывок кода иллюстрирует эту модель:

SOCKET sock;

char buffer[256];

 int done = 0,

err;

while(!done)

{

// прием данных 

err = recv(sock, buffer, sizeof (buffer));

if (err == SOCKET_ERROR)

{

// отработка ошибки приема 

printf("recv failed with error %dn",

WSAGetLastError());

return;

}

// обработка данных 

ProcessReceivedData(buffer);

}

Проблема в приведенном коде состоит в том, что функция recv может никогда не отдать управление приложению если на данный сокет не прийдут какието данные. Некоторые прграммисты проверяют наличие ожидающих данных на сокете вызовом тогоже recv с флагом MSG_PEEK либо ioctlsocket с FIONREAD опцией. Проверка наличия ожидающих данных на сокете без ихнего приема считается в прогаммировании плохим тоном, это надо избегать любой ценной чтением данных из системного буфера. Для избежания этого метода, мы должны не дать приложению полностью застыть из-за отсутсвия ожидающих данных без вызова проверки наличия таковых. Решением данной проблемы может быть разделить приложение на два потока: читаюший и обрабатывающий данные, оба разделяя общий буфер данных. Доступ к которому осуществляется синхронизирующим обьектом как событие(event) или мьютекс(mutex). Задача читающего потока состоит в считвыании поступающих данных из сети на сокет в общий буфер. Когда читающий поток считал минимальное необходимое количество данных преднозначенных для обрабатывающего потока, он переключает событие в отсигналенное состояние, таким образом давая знать обрабатывающему потоку о наличии в общем буфере данных для обработки. Обрабатывающий поток в свою очередь забирает из буфера данные и обрабатывает их.

Следующий кусок кода показывает реализацию данного метода, реализуя две функции: одна обеспечивая чтение данных из сети (ReadingThread), другая обработку данных(ProcessingThread):

 #define MAX_BUFFER_SIZE 4096

// Инициализация critical section

 // и события с автосбросом перед инициализацией потоков

 CRITICAL_SECTION data;

HANDLE hEvent;

SOCKET sock;

CHAR buffer[MAX_BUFFER_SIZE];

 // создание читающего сокета

// читающий поток

void ReadingThread(void)

 {

int nTotal = 0,

nRead = 0,

nLeft = 0,

nBytes = 0;

while (true)

{

nTotal = 0;

nLeft = NUM_BYTES_REQUIRED;

while (nTotal < NUM_BYTES_REQUIRED)

{

EnterCriticalSection(&data);

nRead = recv(sock, &(buffer[MAX_BUFFER_SIZE - nBytes]), nLeft, 0);

if (nRead == -1)

{

printf("errorn");

ExitThread();

}

nTotal += nRead;

nLeft -= nRead;

nBytes += nRead;

LeaveCriticalSection(&data);

}

SetEvent(hEvent);

}

}

// обрабатывающий поток 

void ProcessingThread(void)

{

while (true)

{

// ждем данных 

WaitForSingleObject(hEvent);

EnterCriticalSection(&data);

DoSomeComputationOnData(buffer);

 // удаляем из буфера обработанные данные

 nBytes -= NUM_BYTES_REQUIRED;

LeaveCriticalSection(&data);

 }

}

Основная трудность в програмировании блокирующих сокетов состоит в подержке передачи - приёма данных для более одного сокета. Используя предыдущую реализацию, приложение должно быть изменено для того чтоб иметь по одной паре читающего и обрабатывающего потока на каждый сокет. Это добавляет некоторую рутинную работу для програмиста и осложнение кода. Единственный недостаток состоит в том, что приложение плохо маштабируется при большом количестве сокетов.

1.2. Неблокирующий режим

Альтернативой блокирующим сокетам является неблокирующие. Неблокирующие сокеты являются более перспективными, но ихнее приемущество над блокирующими не велико. Следующий пример показывет как создать сокет и переключить его в неблокирующий режим:

 SOCKET sock;

unsigned long nb = 1;

int err;

sock = socket(AF_INET, SOCK_STREAM, 0);

err = ioctlsocket(sock, FIONBIO, (unsigned long *) &nb);

 if (err == SOCKET_ERROR)

{

//ошибка при переключении сокета в неблокирующий режим

}

После переключения сокета в неблокирующий режим, вызовы Winsock API связанные с приемом, передачей данных либо управлением соединений будут сразу возвращять управление прилоежению, не ожидая завершения текущей операции. В большинстве случаев данные вызовы возвращают ошибку типа WSAEWOULDBLOCK, что означает, что операция не имела времени закончится в период вызова функции. К примеру функция recv вернет WSAEWOULDBLOCK если нет ожидающих данных в системном буфере для данного сокета. Часто нужны дополнительные вызовы функции пока она не вернет сообшение об удачном завершение операции.

Потому как большинство неблокирующих вызовов функции терпят неудачу с ошибкой WSAEWOULDBLOCK, вы должны проверять все коды возвратов и быть готовыми к неудачному вызову в любое время. Многие программисты совершают большую ошибку все время вызывая функцию пока она не вернет удачный код возврата. К примеру постоянный вызов recv в цикле в ожидании прочтения 100 байт данных ничем не лучше чем вызов recv в блокирующем режиме с параметром MSG_PEEK. Winsock модели ввода - вывода могут помоч приложению, определьть когда сокет готов к чтению, либо передаче данных.

Каждый из режимов - блокирующий и неблокирующий - имеют свои недостатки и преимущества. Блокирующие сокеты более легки в использовании с концептуальной точки зрения, но есть затруднения в управлении большого количества соединений, либо когда передаются данные разных обьемов и в разные периоды времени. С другой стороны неблокирующие сокеты более сложны, так как существует необходимость в написание более сложного кода для управления возможностью приема кодов возврата типа WSAEWOULDBLOCK при каждом вызове Winsock API функций. Сокетные модели ввода - вывода помогают приложению справится с управлением передачей данных на одном или более соединений одновременно асинхронным способом.

Модель порта завершения

Последняя модель ввода/вывода, которую мы рассмотрим, это модель "порта завершения" – completion port. Порт завершения представляет собой специальный механизм в составе ОС, с помощью которого приложение использует объединение (пул) нескольких потоков, предназначенных единственно для цели обработки асинхронных операций ввода/вывода с перекрытием.

Приложения, которые вынуждены обрабатывать многочисленные асинхронные запросы (речь идет о сотнях и тысячах одновременно поступающих запросах – например, на поисковых серверах или популярных серверах типа www.microsoft.com), с помощью этого механизма могут обрабатывать I/O- запросы существенно быстрее и эффективнее, чем просто запускать новый поток для обработки поступившего запроса. Поддержка этого механизма включена в Windows NT, Windows 2000, Windows XP и Windows Server 2003 и особенно эффективна для мультипроцессорных систем. Так, демонстрационный программный код, который опубликован в MSDN, рассчитан на 16-ти процессорную аппаратную платформу.

Для функционирования этой модели необходимо создание специального программного объекта ядра системы, который и был назван "порт завершения". Это осуществляется с помощью функции CreateIoCompletionPort(), которая асссоциирует этот объект с одним или несколькими файловыми (сокетными) дескрипторами (см. ниже пример в разделе 4.5.1.1) и который будет управлять перекрывающимися I/O операциями, используя определенное количество потоков для обслуживания завершенных запросов.

Для начала нам необходимо создать программный объект - порт завершения I/O, который будет использоваться, чтобы управлять множественными I/O-запросами для любого количества сокетных дескрипторов. Это выполняется вызовом функции CreateIoCompletionPort(), которая определена как:

HANDLE CreateIoCompletionPort(

 HANDLE FileHandle,

 HANDLE ExistingCompletionPort,

DWORD CompletionKey,

DWORD NumberOfConcurrentThreads

 );

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

Чтобы создать объект порта завершения

Связать дескриптор с портом завершения

Когда Вы первоначально создаете объект порта завершения, интерес представляет единственный параметр - NumberOfConcurrentThreads; первые три параметра не существенны. Параметр NumberOfConcurrentThreads специфичен, потому что он определяет число потоков, которым позволяется выполниться одновременно на порте завершения. По идее, нам нужен только один поток для каждого отдельного процессора, чтобы обслужить порт завершения и избежать переключения контекста потока. Значение для этого параметра равное 0 сообщает системе разрешить иметь столько потоков, сколько процессоров имеется в системе. Следующий вызов создает порт завершения I/O:

CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0, 0);

 В результате функция возвратит дескриптор, который используется, чтобы идентифицировать порт завершения, когда на него будет назначен дескриптор сокета.

Модели ввода / вывода

Select (Выбор)

Эта модель обеспечивает более контролируемый способ блокирования. Хотя она позволяет работать и с блокирующими socket’ами, я остановлюсь на неблокирующем режиме. Принцип этой модели станет понятным, если взглянуть на иллюстрацию:

Диалог программы и WinSock будет следующим:

Программа: «Отправь-ка эти данные»

WinSock: «Я не могу сделать это сейчас»

Программа: «Хорошо, скажешь мне, когда будет наилучший момент, чтоб повторить попытку»

WinSock: «Конечно, повиси минутку»

«Пробуй снова!»

Программа: «Отправь-ка эти данные»

WinSock: «Сделано!»

Вы, возможно, заметили, что эта модель выглядит подобно блокирующему socket’у. Это, потому что select действительно блокирует. Первый вызов пытается выполнить WinSock операцию. В этом случае операция заблокировала бы выполнение основного процесса, но функция не может быть выполнена и она завершается неудачей. Тогда управление передается основному потоку программы, который, в свою очередь, вызывает select-метод (т.е. программа обращается к модели, что бы определить подходящее время для повторной попытки). Он будет ждать наилучшего момента, чтобы повторить WinSock функцию.

Но тут может возникнуть вполне справедливый вопрос: если данная модель блокирует, то почему мы используем ее для неблокирующих socket’ов? Дело в том, что этот способ может «ждать» при многократных событиях. Ниже приведен прототип функции select:

select (nfds:DWORD, readfds:DWORD, writefds:DWORD, exceptfds:DWORD, timeout:DWORD)

Select определяет статус одного или нескольких socket’ов, предоставляя синхронизацию ввода/вывода, если это необходимо. Первый параметр игнорируется, последний параметр используется для определения оптимального времени «ожидания» функции. Остальные параметры определяют набор socket’ов:

readfds – набор socket’ов, которые будут проверены на возможность чтения.

writefds - набор socket’ов, которые будут проверены на возможность записи.

exceptfds - набор socket’ов, которые будут проверены на наличие ошибок.

«Возможность чтения» значит, что данные прибыли на socket, и, что само чтение после select’а аналогично получению данных. «Возможность записи» значит, что сейчас подходящее время для передачи данных, т.к. получатель, возможно, готов принять их. Exceptfds используется, чтобы «словить» ошибки из неблокирующих соединений.

WSAASyncSelect

Большинство оконных программ используют специальные диалоговые окна, что бы получить информацию от пользователя или наоборот. WinSock обеспечивает способ взаимодействия уведомлений о сетевых событиях с обработкой сообщений Windows. Функция WSAAsyncSelect позволяет зарегистрировать уведомление для определенного сетевого события в виде привычного сообщения Windows.

WSAAsyncSelect (s:DWORD, hWnd:DWORD, wMsg:DWORD, lEvent:DWORD)

Эта функция требует специального сообщения (wMsg), которое выбирает пользователь. А оконная процедура должна обработать это самое сообщение. lEvent является битовой маской, которая определяет событие, о котором будет сообщено. Рисунок для данной модели можно сделать таким:

Допустим, что первое сообщение хочет отправить какие-то данные socket’у, используя send. Так как socket неблокирующий, функция будет завершена мгновенно. Вызов функции может завершиться успешно, но тут этого не происходит. Предполагая, что WSAAsyncSelect была настроена таким образом, что сообщит нам о событии FD_WRITE, в конечном итоге мы получим сообщение от WinSock, говорящее нам о том, что данное событие произошло. В данном случае это событие FD_WRITE, которое означает что-то типа «Я готово, попробуй переслать свои данные». Таким образом, в обработчике сообщения программа пытается переслать данные, и эта попытка завершается успехом.

Беседа между программой и WinSock подобна модели select, различие лишь в методе уведомления: оконное сообщение вместо синхронного вызова select’а. В то время как select блокирует основной процесс, ожидая пока произойдет событие, программа, использующая WSAAsyncSelect, может продолжить обработку сообщений Windows до тех пор, пока не происходит никаких событий:

Программа регистрируется для уведомления о сетевых событиях через оконные сообщения

Программа: «Отправь-ка эти данные»

WinSock: «Я не могу сделать это сейчас»

Программа обрабатывает некоторое сообщение

Программа обрабатывает другое сообщение

Программа получает уведомляющее сообщение от WinSock

Программа: «Отправь-ка эти данные»

WinSock: «Сделано! »

WSAAsyncSelect обеспечивает более «Windows’овский» способ уведомления и он довольно прост в использовании. Для серверов с низкой пропускной способностью (меньше 1000 соединений) этот способ вполне хорош. Недостатком является то, что оконные сообщения, сами по себе, не очень быстрые, а так же в том, что для использования этой модели требуются окна (т.е. программа должна быть GUI).

WSAEventSelect

Примечание: под «объектом события» далее будет пониматься какое-то определенное сетевое событие. Дело в том, что тут событие рассматривается, как класс =).

WSAEventSelect можно назвать родственником WSAAsyncSelect, который работает очень похожим способом, но вместо оконных сообщений использует объекты событий. В этом есть определенные преимущества, одним из которых является эффективность (объекты событий работают быстрее оконных сообщений). Графическая интерпретация этой модели выглядит немного сложнее, чем предыдущей, но на самом деле это не так:

Программа регистрируется для уведомления о сетевых событиях через объекты событий

Программа: «Отправь-ка эти данные»

WinSock: «Я не могу сделать это сейчас»

Программа ждет события, чтобы сигнализировать о нем

Программа: «Отправь-ка эти данные»

WinSock: «Сделано! »

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

Поначалу эта модель похожа на блокирующую: Вы ждете событие, о котором Вам будет сообщено. Это верно, но в тоже самое время Вы можете создать свой объект события. Все объекты события являются частью WinAPI, которую использует WinSock. В WinSock есть некоторые функции для создания объектов, но фактически это API функции в WinSock упаковке.

Все, что WinSock делает в этой модели, это сигнализирует объект события, когда это событие должно произойти.

Функция, с помощью которой регистрируется сетевое событие WSAEventSelect:

WSAEventSelect (s:DWORD, hEventObject:DWORD, lNetworkEvents:DWORD)

WSAAsyncSelect отправит Вам сообщение о произошедшем сетевом событии (FD_READ, FD_WRITE, и т.д.) В отличие от WSAAsyncSelect, у WSAEventSelect есть только один способ уведомления: сигнализирование объекта событий. Это позволяет использовать данную модель как в GUI приложениях, так и в консольных. Какие события произошли можно узнать с помощью WSAEnumNetworkEvents.

Введение в перекрытый ввод/вывод

Перекрытый ввод/вывод является очень эффективным, особенно при хорошей реализации (позволяет обрабатывать очень много соединений). Это особенно касается сочетания перекрытого ввода/вывода с портами завершения. Как я говорил ранее, в большинстве случаев использование перекрытого ввода/вывода излишне, но я все равно постараюсь раскрыть эту тему, хотя бы, для общего развития.

В рассмотренных ранее моделях посылались некоторые уведомления (такие как «данные доступны» или «готов попробовать переслать данные» и т.д.) при возникновении определенных сетевых событий. Перекрывающие модели также посылают уведомления, но не о наступлении сетевых событий, а об их завершении. При вызове WinSock функции она может либо успешно завершиться, либо завершиться неудачей с кодом ошибки WSA_IO_PENDING. При использовании перекрывающих моделей Вы будете уведомлены о завершении операции. Это значит, что Вам просто надо подождать, пока операция не завершится.

Ценой этого эффективного подхода является трудная реализация. Если Вам не требуется действительно хорошая эффективность, то лучше воспользоваться ранее описанными моделями. Кроме того, операционные системы Windows 9x/ME не полностью поддерживают перекрытые модели ввода/вывода.

Как и модели с уведомлением о сетевых событиях, перекрытие модели так же могут быть реализованы по-разному. Они отличаются способом уведомления: блокирование, polling, процедуры завершения и порты завершения.

Перекрытый ввод/вывод: блокирование

Первая модель перекрытого ввода/вывода, о которой я расскажу, использует объект события для сигнализирования о завершении. Эта модель во многом похожа на WSAEventSelect, но отличием является то, что объект устанавливается в сигнализированное состояние при завершении WinSock операции, а не при наступлении какого-то сетевого события.

Программа: «Отправь-ка эти данные»

WinSock: «Окей, но я не могу отправить их прямо сейчас»

Программа ждет сигнала от объекта события, указывающего на то, что функция завершена

Из рисунка и диалога понятна суть работы этой модели. Как Вы можете видеть, WinSock функция происходит одновременно с работой главного потока программы (в данном примере главный поток ожидает сигнала от события). Когда событие получает сигнал от WinSock функции (пунктирная линия на рисунке) оно переходит в сигнализированное состояние и отправляет сигнал главному потоку о том, что функция завершена, и главный поток выполняет обработку полученного сигнала и переходит к выполнению следующей команды.

Перекрытый ввод/вывод: polling

Так же как и в ранее упомянутой модели polling, в этой модели так же можно запросить статус выполнения операции (хотя в ранее описанном polling’e мы не запрашивали статус выполнения, а просто получали данные о неудачном завершении функции. Но основной поток программы знал, когда функция завершилась неудачно, а когда наоборот). С помощью функции WSAGetOverlappedResult можно узнать статус выполняемой операции. Графическая интерпретация перекрытого polling’а очень похожа на интерпретацию обычного polling’а, за исключением того, что WinSock функция выполняется в то же время, что и опрос программы о выполнении функции.

Программа: «Отправь-ка эти данные»

WinSock: «Окей, но я не могу отправить их сейчас»

Программа: «Уже отправил?»

WinSock: «Нет»

Программа: «Уже отправил?»

WinSock: «Нет»

Программа: «Уже отправил?»

WinSock: «Нет»

Программа: «Уже отправил?»

WinSock: «Да! »

И тут я повторюсь: эта модель не очень хороша, так как она приводит процессор в панику. Поэтому я не рекомендую использовать эту модель.

Перекрытый ввод/вывод: процедуры завершения

Процедуры завершения – процедуры обратного вызова (т.е. вызываются в ответ на определенное действие. Далее я буду называть эти процедуры процедурами отзыва), которые вызываются при завершении операции. Тут вроде бы все просто, но есть одна хитрость: эти процедуры вызываются в контексте потока, который начал операцию. Что это значит? Представьте себе поток, который запросил перекрытую операцию записи. WinSock выполняет эту операцию, в то время как Ваш поток тоже выполняется. Таким образом у WinSock есть свой собственный поток для этой операции. Когда операция закончится, WinSock должен вызвать процедуру отзыва. Если это произойдет, то вызванная процедура будет выполняться в контексте потока WinSock. Это означает, что поток, вызвавший операцию записи, будет выполняться одновременно с процедурой вызова. Проблема состоит в том, что синхронизация с вызывающим потоком отсутствует, и он не знает, завершена ли операция (только если ему не сообщат об этом из параллельного потока).

Что бы избежать этого, WinSock удостоверяется в том, что процедура отзыва протекает в том же потоке, из которого происходил запрос. Осуществляется это с помощью APC (Asynchronous Procedure Call или Асинхронный Вызов Процедуры), механизма, встроенного в Windows. Это можно представить как «внедрение» процедуры в основной поток выполнения программы. Таким образом, поток сначала выполнит процедуру, а потом будет делать, то, что делал до ее «внедрения». Естественно, что система не может приказать потоку: «Прекрати делать все, что делал и обработай сначала эту процедуру».

Для того, что бы обеспечить «внедрение» в нужное место механизм APC требует, что бы поток находился в, так называемом, извещающем состоянии ожидания. Каждый поток имеет свою собственную APC очередь, в которой процедуры ждут своего вызова. Когда поток входит в извещающее состояние ожидания, это указывает на то, что он готов обслуживать очередь APC.

Перекрытый ввод/вывод с процедурами завершения использует APC для уведомления о завершении операции.

Программа: «Отправь-ка эти данные»

WinSock: «Окей, но я не могу отправить их сейчас»

Программа входит в извещающее состояние ожидания

Функция завершилась

Состояние ожидания получает сигнал, о том, что функция завершена

Выполняется функция отзыва и управление переходит к программе

APC может быть немного трудным для понимания, но это не страшно. Это всего лишь краткое введение. Обычно поток находится в состоянии ожидания до тех пор, пока не будет вызвана процедура отзыва, которая обрабатывает событие (о том, что WinSock функция завершена) и возвращает управление программе. Потом главный поток производит необходимые операции (если таковые имеются) и возвращается в состояние ожидания снова.

Перекрытый ввод/вывод: порты завершения

Наконец мы подошли к последней и, возможно, самой эффективной модели ввода/вывода: перекрытый ввод/вывод с портами завершения. Порт завершения – механизм, доступный в NT ядрах ОС (9x не поддерживают их), позволяющий эффективно управлять потоками. В отличие от всех рассмотренных моделей, «порты завершения» обладают собственным управлением потоков. Как Вы могли заметить, все предыдущие иллюстрации представляли собой что-то типа графиков зависимости от времени. Для этой модели я не делал такого графика и диалога программы с WinSock, т.к. это вряд ли поможет прояснить ситуацию. Вместо этого я изобразил образ самого механизма, который хорошо показывает, что происходит:

Идея в следующем: после создания порта завершения socket’ы могут быть связаны с ним. С этой точки зрения, когда операция перекрытого ввода/вывода завершится, соответствующее уведомление отправляется на порт завершения. На порте есть подобные рабочие потоки, которые заблокированы. При прибытии уведомления порт берет один поток из очереди неактивных потоков и делает его активным. Этот поток обрабатывает поступившее завершенное событие и блокируется на порту.

В порту завершения есть определенное ограничение по количеству потоков, но обычно не все из них активны в одно и тоже время, что позволяет сокращать очередь потоков. Создавая подобный порт, Вы можете указывать, сколько потоков будет активными.

В данной модели нет никакой связи между потоком и соединением. Каждый поток может взаимодействовать с событием, пришедшим на порт. Данную модель не просто реализовать, но реализовав, ее Вы сможете работать с тысячами соединений.


 

А также другие работы, которые могут Вас заинтересовать

2600. Моя професія - бухгалтер. Професійна орієнтація 190 KB
  Професійна орієнтація — система організації та проведення навчально-виховної роботи, спрямованої на засвоєння студентами необхідних знань про соціально-економічні і психофізіологічні умови правильного вибору професії, формування у них уміння аналізувати вимоги професії до психологічної структури особистості
2601. День Соборності України – день єднання народу 42.08 KB
  Психолого-педагогічне обґрунтування обрання теми та форми виховного заходу. Було обрано тему День Соборності України – день єднання народу у формі класної години. Необхідність проведення заходу виникла  у зв’язку зі святом 22 січня...
2602. Внеклассное мероприятие по теме Весна пришла 88.61 KB
  Форма проведения: соревнование двух команд Цели: развитие памяти, внимания, наблюдательности, смекалки, активизация мыслительной деятельности, формирование у учащихся навыков самостоятельной работы; воспитание духа соревнования, честности, целе...
2603. Защитники Родины 28.24 KB
  Тема: Защитники Родины. Форма проведения: игра-соревнование; Цель: расширить представление детей об общенародном празднике, посвященном вооруженным Силам России, воспитывать у них любовь к защитникам Отечества и к своей Родине, создать атмосферу п...
2604. Масленица. Воспитательное мероприятие 29.91 KB
  Цели: Познакомить учащихся с русскими традициями масленичной недели. Задачи: -  Воспитывать уважение и интерес к культурному наследию нашей страны.- Формировать у учащихся чувства коллективизма и взаимопомощи, дисциплинированности. Материалы и ...
2605. Основы денежно-кредитного регулирования экономики 280 KB
  ТЕМА 1. Основы денежно-кредитного регулирования экономики Введение в курс Денежно-кредитное регулирование экономики. Денежный оборот и его структура Основные потоки денег в экономике Инфляция как социально-экономический процесс: понятие, причины...
2606. Изучение маятника максвелла 52.11 KB
  Изучение маятника максвелла Цель работы: определение основных характеристик маятника Максвелла. Приборы и принадлежности: установка FPM-03, набор колец, штангенциркуль. Краткие теоретические сведения Движение твёрдого тела можно рассматривать как дв...
2607. Изучение движения маятника максвелла 119.5 KB
  Цель работы: ознакомление со сложным движением твердого тела на примере маятника Максвелла: экспериментальное определение момента инерции тел вращения. МЕТОДИКА ЭКСПЕРИМЕНТА Маятник Максвелла представляет собой однородный металлический диск, в серед...
2608. Измерение ускорения свободного падения с помощью маятников 133 KB
  Цель работы: измерить ускорение свободного падения с помощью физического (оборотного) и математического маятников. МЕТОДИКА ЭКСПЕРИМЕНТА Все тела или совокупности тел, которые могут совершать периодические движения, или колебания, называются колебат...