Серверные 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.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: