Системное программирование в среде Windows
Шрифт:
Если не оговорено иное, типом сокетов в наших примерах всегда будет SOCK_STREAM. Сокеты типа SOCK_DGRAM рассматривается далее в этой главе.
Связывание сокета
Следующий шаг заключается в привязке сокета к его адресу и конечной точке (endpoint) (направление канала связи от приложения к службе). Вызов socket, за которым следует вызов bind, аналогичен созданию именованного канала. Однако не существует имен, используя которые можно было бы различать сокеты данного компьютера. Вместо этого в качестве конечной точки службы используется номер
s — несвязанный сокет, возвращенный функцией socket.
saddr — заполняется перед вызовом и задает протокол и специфическую для протокола информацию, как описано ниже. Кроме всего прочего, в этой структуре содержится номер порта.
namelen — присвойте значение sizeof (sockaddr).
В случае успешного выполнения функция возвращает значение 0, иначе SOCKET_ERROR. Структура sockaddr определяется следующим образом:
Первый член этой структуры, sa_family, обозначает протокол. Второй член, sa_data, зависит от протокола. Internet-версией структуры sa_data является структура sockaddr_in:
Обратите внимание на использование типа данных short integer для номера порта. Кроме того, номер порта и иная информация должны храниться с соблюдением подходящего порядка следования байтов, при котором старший байт помещается в крайней позиции справа (big-endian), чтобы обеспечивалась двоичная совместимость с другими системами. В структуре sin_addr содержится подструктура s_addr, заполняемая уже знакомым нам 4-байтовым IP-адресом, например 127.0.0.1, указывающим систему, чей запрос на образование соединения должен быть принят. Обычно удовлетворяются запросы любых систем, в связи с чем следует использовать значение INADDR_ANY, хотя этот символический параметр должен быть преобразован к корректному формату, как показано в приведенном ниже фрагменте кода.
Для преобразования текстовой строки с IP-адресом к требуемому формату можно использовать функцию inet_addr, поэтому член sin_addr.s_addr переменной sockaddr_in инициализируется следующим образом:
О связанном сокете, для которого определены протокол, номер порта и IP-адрес, иногда говорят как об именованном сокете (named socket).
Перевод
Функция listen делает сервер доступным для образования соединения с клиентом. Аналогичной функции для именованных каналов не существует.
Параметр nQueueSize указывает число запросов на соединение, которые вы намерены помещать в очередь сокета. В версии Winsock 2.0 значение этого параметра не имеет ограничения сверху, но в версии 1.1 оно ограничено предельным значением SOMAXCON (равным 5).
Прием клиентских запросов соединения
Наконец, сервер может ожидать соединения с клиентом, используя функцию accept, возвращающую новый подключенный сокет, который будет использоваться в операциях ввода/вывода. Заметьте, что исходный сокет, который теперь находится в состоянии прослушивания (listening state), используется исключительно в качестве параметра функции accept, а не для непосредственного участия в операциях ввода/вывода.
Функция accept блокируется до тех пор, пока от клиента не поступит запрос соединения, после чего она возвращает новый сокет ввода/вывода. Хотя рассмотрение этого и выходит за рамки данной книги, возможно создание неблокирующихся сокетов, а в сервере (программа 12.2) для приема запроса используется отдельный поток, что позволяет создавать также неблокирующиеся серверы.
s — прослушивающий сокет. Чтобы перевести сокет в состояние прослушивания, необходимо предварительно вызвать функции socket, bind и listen.
lpAddr — указатель на структуру sockaddr_in, предоставляющую адрес клиентской системы.
lpAddrLen — указатель на переменную, которая будет содержать размер возвращенной структуры sockaddr_in. Перед вызовом функции accept эта переменная должна быть инициализирована значением sizeof(struct sockaddr_in).
Отключение и закрытие сокетов
Для отключения сокетов применяется функция shutdown(s, how). Аргумент how может принимать одно из двух значений: 1, указывающее на то, что соединение может быть разорвано только для посылки сообщений, и 2, соответствующее разрыву соединения как для посылки, так и для приема сообщений. Функция shutdown не освобождает ресурсы, связанные с сокетом, но гарантирует завершение посылки и приема всех данных до закрытия сокета. Тем не менее, после вызова функции shutdown приложение уже не должно использовать этот сокет.
Когда работа с сокетом закончена, его следует закрыть, вызвав функцию closesocket(SOCKET s). Сначала сервер закрывает сокет, созданный функцией accept, а не прослушивающий сокет, созданный с помощью функции socket. Сервер должен закрывать прослушивающий сокет только тогда, когда завершает работу или прекращает принимать клиентские запросы соединения. Даже если вы работаете с сокетом как с дескриптором типа HANDLE и используете функции ReadFile и WriteFile, уничтожить сокет одним только вызовом функции CloseHandle вам не удастся; для этого следует использовать функцию closesocket.