Библиотека Winsock.dll, как и библиотеки UNIX систем, основана на реализации нескольких функций для работы с гнёздами.
Первая функция WSAStartup. Она начинает процесс работы с библиотекой WinSock.dll.
int WSAStartup (
WORD wVersionRequested,
LPWSADATA lpWSAData
);
wVersionRequested -Старший байт определяет минимальную версию WinSock.dll, которую вы сможете использовать, а младший - старшую версию.
lpWSAData - указатель на WSAData, которая получить дополнительную информацию.
Эта функция должна вызываться первой при работе с гнёздами. Ну, здесь сложно что-то говорить, просто давай рассмотрим примерчик, скажу только, что функция эта возвращает код ошибки:
WORD wVersionRequested;
WSADATA wsaData;
int err;
// Здесь создаётся запрос на возможные библиотеки от
// нулевой версии до второй
wVersionRequested = MAKEWORD( 2, 0 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
// Если функция вернула нулик, то ошибок нет, так
// что здесь можешь обрадывать пользователя.
return;
}
// Теперь проверим, что нам записали в WsaData
// Если wVersion не равны 0 или 2 то значит система
// использует более новую версию и придётся
// закрывать программку
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 0 ) {
// сообщи пользователю, что у него нет нужной Winsock.dll
// и закрывай программку
WSACleanup( ); // Это нужно, чтобы закончить работу
return;
}
Вторая функция, которую мы рассмотрим, будет socket. Она устанавливает оконечную точку линии связи.
SOCKET socket (int af, int type, int protocol);
аf - обозначает домен. Возможны следующие варианты данного параметра:
AF_UNSPEC - непредусмотренный;
AF_UNIX - "UNIX System" канальная связь локальной машины с сервером;
AF_INET - интернет протоколы TCP, UDP и так далее.
Полный список возможных параметров вы можете найти в winsock.h или winsock2.h, но должен заметить, что AF_UNSPEC, продолжает включатся в заголовочные файлы, но в самой функции не используется и не влияет на её работу, поэтому не рекомендую её использовать.
type - тип связи через гнездо (виртуальный канал или дейтаграмма). Возможны только два значения этого параметра:
SOCK_STREAM - используется TCP для адресов семейства Интернет, виртуальный канал;
SOCK_DGRAM - используется UDP для адресов семейства Интернет, дейтаграмма;
Protocol - ну протокол он и в Африке протокол, поэтому возможные варианты смотри в заголовочных файлах.
И наконец, функция возвращает значение типа SOCKET, это у нас будет дескриптор гнезда. Засунь это значение куда-нибудь, оно нам ещё пригодится, только сначала проверь, не равно ли это значение SOCKET_ERROR. Если так, то произошла ошибка, а код ошибки ты можешь узнать функцией WSAGetLastError. Например, код ошибки равный WSANOTINITIALISED означает, что ты должен выполнить сначала WSAStartup (об этом чуть позже), или WSAENETDOWN означает, что твоя сеть грохнулась.
Теперь третья интересная функция, которая пригодиться тебе для программирования гнёзд - bind. Эта функция связывает дескриптор гнезда с именем:
int bind (SOCKET s, const struct sockaddr FAR* name, int namelen);
s - дескриптор гнезда. Помнишь, что тебе вернула наша первая функция socket, теперь вспомни, куда ты это засунул, так как тот дескриптор гнезда нужно вставлять сюда.
name - адрес структуры, определяющей идентификатор, характерный для данной комбинации домена и протокола в функции socket (вот это я ляпнул). Короче это структура типа такой:
namelen - длина структуры name; без этого параметра ядро не знало бы, какова длина структуры, поскольку она может различаться, в зависимости от доменов и протоколов.
Ну и что же нам возвращает эта функция? А возвращает она код ошибки. Если он равен нулю, то можешь прыгать до потолка, а если нет, то ты увидишь SOCKET_ERROR. А код ошибки ты можешь узнать всё той же функцией WSAGetLastError. Ошибки примерно те же самые как и в функции socket.
Следующая функция будет connect. Эта функция отправляет запрос на подключение к существующему гнезду. Эта функция вызывается на машине клиента, для связи с сервером:
int connect (
SOCKET s,
const struct sockaddr FAR* name,
int namelen
);
Я думаю, хватит расписывать все функции. Как ты заметил, в их реализации много общего, а именно структура SOCKET и возвращаемое значение, поэтому их описание я буду опускать, а перейдём сразу к name.
Name - указывает на выходное гнездо, которое образует противоположный конец линии связи
Namelen - Длинна параметра Name
Оба гнезда должны использовать одни и те же домен и протокол связи, в таком случае ядро возвращает правильность установки линии связи. Если вы использовали для инициализации гнезда виртуальный канал, то произойдёт соединение двух гнёзд, а если тип гнезда дейтаграмма, сообщаемый функцией connect адрес будет использоваться в последующих обращениях к функции send через данное гнездо, в этом случае в момент вызова никаких соединений не производится.
Следующая функция Listen вызывается на машине сервере. Эта функция служит для начала прослушивания гнезда на случай подключения к нему со стороны клиента:
int listen (SOCKET s, int backlog);
s - дескриптор гнезда.
backlog - максимально-допустимое число запросов, ожидающих обработки. Если этот параметр равен SOMAXCONN, то ядро само установит максимальное значение.
В большинстве случаев, параметр blocklog зависит от установленного в системе параметра "максимальное количество подключений". Если вы используете Windows 95/98, то этот параметр регулируется в настройках сети.
Ну и на конец для подтверждения сервером соединения необходимо вызвать accept. Эта функция принимает запросы на подключение, поступающие на вход процесса-сервера:
addr - указатель на структуру, в котором ядро возвращает адрес подключаемого клиента
addrlen - размер пользовательского массива
По завершении выполнения функции ядро записывает в переменную addrlen длину параметра addr. Функция возвращает новый дескриптор гнезда, отличный от дескриптора s. Процесс-сервер может продолжать слежение за состоянием объявленного гнезда, поддерживая связь с клиентом по отдельному каналу.
Вот мы и закончили рассматривать функции необходимые тебе для соединения процесса-клиента и процесса-сервера. Немного позже мы рассмотрим пример использования всего того, что мы тут наболтали, а пока давай ещё потрудимся над тем, как посылать и принимать пакеты и начнём мы с функции отправки пакетов, потому что для того, чтобы что-то принять, необходимо сначала отправить. И поможет нам в отправке пакетов функция send.
int send (
SOCKET s,
const char FAR * buf,
int len,
int flags
);
s - как всегда это дескриптор гнезда
buf - указатель на посылаемые данные
len - размер данных
Функция возвращает количество фактически переданных байт.
Параметр flags может содержать значение MSG_DONTROUTE - определяет, что данные не должны быть подчиненны маршрутизации, MSG_OOB (послать данные out-of-band - "через таможню"), если посылаемые данные не учитываются в общем информационном обмене между взаимодействующими процессами.
Длинна сообщения не должна превышать значения в SO_MAX_MSG_SIZE.
Приём данных осуществляется функцией recv:
int recv (
SOCKET s,
char FAR* buf,
int len,
int flags
);
buf - массив для приема данных
len - ожидаемый объем данных
Flags - могут быть установлены таким образом, что поступившее сообщение после чтения и анализа его содержимого не будет удалено из очереди, или настроены на получение данных out-of-band.
MSG_PEEK - Данные будут скопированы в буфер, но не удалены из входной очереди.
MSG_OOB - то же что и в функции send.
Возвращаемое значение - количество байт, фактически переданных пользовательской программе
Для дейтаграммных версиях используются функции sendto и recvfrom. Обе функции работают так же как и send и recv, только в качестве дополнительных параметров указываются адреса.
Теперь мы научились получать соединения, посылать данные, осталось только научится закрывать соединения. Функция shutdown закрывает гнездовую связь:
int shutdown (
SOCKET s,
int how
);
mode - указывает, какой из сторон (посылающей, принимающей или обеим вместе) отныне запрещено участие в процессе передачи данных. Если SD_RECEIVE, то последующие сообщения от разъёма будут отвергнуты. SD_SEND - отвергает последующие отправки сообщений. SD_BOTH - отвергает и приём, и передачу сообщений.
Функция сообщает используемому протоколу о завершении сеанса сетевого взаимодействия, оставляя, тем не менее, дескрипторы гнезд в неприкосновенности. Но эта функция не освобождает дескриптор гнезда.
Освобождение дескриптора гнезда происходит функцией closesocket:
int closesocket (SOCKET s);
s - дескриптор гнезда.
Ну вот мы и закончили рассматривать основные функции для работы с гнёздами. Я не могу, даже заикнутся о том, что мы рассмотрели все функции, это были только основные, все функции мы не сможем рассмотреть, но ещё с некоторыми из существующих мы познакомимся в процессе рассмотра примеров.
Конечно, полученных знаний тебе хватит, чтобы самостоятельно написать простенькую программу, а дополнительную информацию получить их помощи, но лучше если ты немного наберёшься терпения и подождёш следующего выпуска журнала.