Сокеты
Возможная схема использования сокетов в случае работы N клиентов с одним WEB-сервером показана на рис. 4.1. Клиент 1 сформировал два соединения с сервером.
Схема взаимодействия различных операторов winsock в рамках идеологии клиент/сервер для случая процедур, ориентированных на соединение, показана на рисунке 4.2. Горизонтальными стрелками обозначены направления посылки сетевых сообщений.
Следует иметь в виду, что программе клиента (выделена рамкой) в этом режиме не нужно знать номер порта, поэтому она не обращается к процедуре bind, а для установления связи сразу вызывает оператор connect. Современные распределенные информационные системы, WWW-серверы, поисковые системы и т.д. эффективно используют механизмы формирования сокетов и многие процедуры, описанные в данном разделе. Из литературы [2.15, 2.24] известно, что для многих видов услуг в Интернет выделены строго определенные номера портов. Доступ же к этим услугам должен быть обеспечен достаточно большому числу пользователей. С клиентской стороны при этом используются номера портов со значениями из диапазона 1024-5000. Для каждого нового клиентского запроса в ЭВМ-сервере, как правило, формируется новый процесс.
Лишь при успешной реализации всех перечисленных операций может начаться обмен данными. Для пересылки информации могут использоваться команды write, read, send, recv. Команды write и read имеют форму вызова:
R=write(s, buf, len) или R=read(s, buf, len),
где s — дескриптор сокета, buf — имя массива, подлежащего пересылке (или предназначенного для приема), len — длина этого массива. Оператор writev отличается от write тем, что данные могут не лежать в виде непрерывного массива:
R=writev(s, io_vect, vectlen) или R=readv(s, io_vect, vectlen),
где s — дескриптор сокета, io_vect — вектор указатель на список указателей, vectlen — длина списка указателей. Команда выполняется медленнее, чем write или read. Список указателей имеет формат (рис. 4.3):
Команды send(s, msg_buf, buflen, flags) и recv имеют аналогичный формат, но среди параметров обращения содержат переменную flags, которая служит для целей диагностики и управления передачей данных (например, пересылка информации с высоким приоритетом ( MSG_OOB — Message Out Of Band ), что используется, в частности, при передаче звуковых сообщений). При работе с операторами send или recv надо быть уверенным, что принимающая сторона знает, что ей следует делать с этими приоритетными сообщениями. Другой возможный флаг, определяемый константой MSG_PEEK, позволяет анализировать запросы из входной очереди транспортного уровня. Обычно после считывания данных из входной очереди они уничтожаются. Когда MSG_PEEK=1, данные из входной очереди не стираются. Этот флаг используется, например, программой FTP. При успешном выполнении команды будет возвращено число переданных байтов, в противном случае —1.
Все перечисленные выше операторы рассчитаны на применение в рамках протоколов, ориентированных на установление соединения (TCP), где не требуется указание адреса места назначения. В протоколах типа UDP (не ориентированных на соединение) для передачи информации используются операторы sendto, recvfrom или sendmsg:
R=sendto(s, msg_buf, buflen, flags, adr_struc, adr_struc_len)
или recvfrom(s, msg_buf, buflen, flags, adr_struc, adr_struc_len),
где s — дескриптор сокета, msg_buf — указатель на буфер, где лежит сообщение, buflen — длина этого буфера (длина сообщения), adr_struc — адресная структура, содержащая исчерпывающую информацию об адресате, adr_struc_len — длина этой структуры. Оператор recvfrom принимает все данные, приходящие на его порт. Приняв дейтограмму, recvfrom записывает также адрес, откуда эта дейтограмма получена. Сервер может посылать по этому адресу дейтограмму-отклик. Вызов оператора sendmsg имеет форму:
R=sendmsg(s, msg_struc, flags) [или recvmsg(s, msg_struc, flags)],
где s — дескриптор сокета, msg_struc — информационная структура, формат которой показан ниже на рисунке 4.4. Применение структур делает программирование пересылки сообщений более гибким. Следует учитывать, что для обменов, не ориентированных на соединение, сокет как бы состоит лишь из одной половины (IP-адрес и номер порта). Сокеты, созданные однажды для обмена (UDP), далее могут жить своей жизнью. Они могут принимать пакеты от других аналогичных "сокетов" и сами посылать им дейтограммы (кавычки здесь связаны с тем, что это не реальный сокет и никакого соединения здесь не осуществляется).
Взаимодействие операторов winsock для систем, не ориентированных на соединение, показано на рисунке 4.5. Здесь так же, как и в случае, ориентированном на соединение, сервер вызывает socket и bind, после чего обращается к процедуре recvfrom (вместо read или recv ). Программа-клиент в данной схеме обращается к оператору bind и совсем не использует оператор connect (ведь предварительного соединения не нужно). Для передачи запросов и приема откликов здесь служат операторы sendto и recvfrom, соответственно.
Помимо уже описанных операторов для работы с сокетами имеется еще один — select, довольно часто используемый серверами. Оператор select позволяет процессу отслеживать состояние одного или нескольких сокетов. Для каждого сокета вызывающая программа может запросить информацию о статусе read, write или error. Форма обращения имеет вид:
R=select(num_of_socks, read_socks, write_socks, error_socks, max_time),
где num_of_socks — число контролируемых сокетов (в некоторых реализациях не используется и является необязательным; по умолчанию это число не должно превышать 64).
В версии Беркли read_socks, write_socks и error_socks представляют собой побитовые маски, определяющие тип сокета. Параметр read_socks — это указатель на структуру, описывающую набор сокетов, состояние которых проверяется на возможность чтения (версия winsock ). Если сокет находится в состоянии listen, он будет помечен как "готов для чтения", при условии, что запрос на соединение уже получен. Это предполагает выполнение оператора accept без блокировки. Для других сокетов "готовность к чтению" подразумевает наличие в очереди запросов чтения. Для сокетов типа SOCK_STREAM это означает, что виртуальный сокет, соответствующий данному сокету, закрылся и операторы recv или recvfrom будут выполнены без блокировки. Если виртуальное соединение закрыто корректно, оператор recv вернет код 0, в противном случае (например, принудительное закрытие) будет возвращен код WSAECONNRESET. Параметр write_socks — указатель на набор сокетов, состояние которых проверяется на возможность записи. Если сокет находится в процессе выполнения процедуры connect, "способность к записи" означает, что установление связи завершено. Для других сокетов это значит, что операции send или sendto будут выполнены без блокировки.
Параметр error_socks — это указатель на набор сокетов, проверяемых на ошибки. В некоторых реализациях этот аргумент идентифицирует список сокетов, помеченных как приоритетные. Сокет помечается как приоритетный, если опция SO_OOBINLINE=FALSE. В случае ошибки оператор select отмечает сокет, где это произошло. Select работает лишь с теми сокетами, которые были выделены с помощью масок. При успешном выполнении оператор возвращает число сокетов, готовых к операциям ввода/вывода, и модифицирует коды масок в соответствии с состоянием сокетов. Прикладная программа может использовать результаты вызова оператора select, анализируя полученные коды масок. Аргумент max_time определяет максимальное время, выделенное select для завершения своей работы. Для уточнения типа ошибки, возникшей при исполнении операции select, можно воспользоваться процедурой WSAGetLastError.
Другим важным оператором является closesocket(s), который закрывает канал сокета с одной из сторон. Все описанные выше операторы (кроме socket, bind и listen ) блокируют работу программы до своего завершения. Практически любая операция, непосредственно связанная с выполнением процедур ввода/вывода, может блокировать выполнение других прикладных функций winsock.
Для обслуживания прикладных процессов (например, WWW-сервера, работа с распределенными базами данных и пр.) разработано много других сервисных программ (WINSOCK.DLL), перечень которых представлен в таблице 4.1.
Большинство перечисленных команд имеют развитую систему диагностики, кроме того, во многих реализациях Unix существует много других полезных команд, описание которых вы можете найти в инструкциях по использованию системы Unix. Рассмотрим некоторые из них.
Программа ioctlsocket(s, long cmd, u_long FAR*argp) служит для получения параметров сокета (выполнение не зависит от типа протокола и коммуникационной субсистемы). Аргумент cmd представляет собой код команды, которая будет выполнена для сокета s, argp — указатель на параметр команды. Возможно применение команд: FIONBIO — разрешает/запрещает режим блокировки сокета s (команда WSAAsyncSelect ставит сокет в режим запрета блокировок автоматически); FIONREAD — определяет объем данных, которые могут быть автоматически считаны через сокет s ; SIOCATMARK — задает режим чтения приоритетной информации (для сокетов типа SOCK_STREAM ).