Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
МетУказКурсРабСистемПрогрЗИЭИТ2005.doc
Скачиваний:
6
Добавлен:
13.09.2019
Размер:
698.37 Кб
Скачать

Создание и использование сокетов

Сокет – это абстрактный объект для обозначения одного из концов сетевого соединения. Он предназначен для создания механизма обмена данными.

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

SOCKET socket (int af, int type, int protocol);

Например, вызов функции

socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) создает сокет потока, использующий протокол ТСР.

socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP) создает сокет дейтаграмм, использующий протокол UDP.

Использование в качестве первого параметра AF_INET означает, что с этим сокетом будут использоваться адреса Internet.

Если третий параметр сделать равным 0, протокол будет выбран автоматически в зависимости от семейства адресов и типов сокета для выбора протокола рекомендуется именно этот способ.

Если функция socket() выполняется успешно, она возвращает дескриптор нового сокета. Если же ее работа завершается аварийно, возвращает значение 0 (INVALID_SOCKET). Для получения более подробной информации об ошибке следует вызвать функцию WSAGetLastError().

Закрытие сокета

Для завершения работы сокета необходимо закрыть его с помощью функции

int closesocket (SOCKET s);

В функцию передается дескриптор сокета.

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

Функция shutdown()

int shutdown (SOCKET s, int how);

Эта функция сообщает гнезду о необходимости прекратить отправку или прием данных в зависимости от указанного метода прекращения работы (how);. Разрешенные методы перечислены в таблице.

Методы прекращения работы гнезда

Значение

Метод

0

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

1

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

2

Не разрешены ни прием, ни отправление данных. Такой метод прекращения работы используется для некоторых реализаций WinSock.

Функция bind()

Недостаточно просто послать пакет на IP-адрес компьютера. Для того, чтобы система поняла, какому процессу этот пакет адресован, необходимо использовать номер порта. Для привязки сокета приложение может использовать любой номер порта от 1 до 65535, хотя этот диапазон обычно подразделяется на поддиапазоны:

Порт 0 Не используется. Если передать 0 в качестве номера порта, то будет автоматически выбран неиспользуемый порт с номером между 1024 и 5000.

Порты 1-255 Зарезервированы для сетевых служб: FTP, telnet, finger и т. д.

Порты 256-1023 Зарезервированы для других служб общего назначения, например функций маршрутизации.

Порты 1024-4999 Служат для портов клиентов. Обычно сокеты приложений-клиентов используют номера портов именно из этого диапазона.

Порты 5000-65535 Используются для определяемых пользователем портов приложений-серверов. Если клиенту необходимо знать номер порта заранее, то для него должен использоваться номер из этого диапазона.

Чтобы связать socket с фактическим адресом хоста и номером порта, приложения обычно вызывают функцию bind().

int bind (SOCKET s, const struct sockaddr FAR *addr, int namelen);

Первым параметром является дескриптор сокета (который возвращен функцией socket() ). Второй параметр является указателем на структуру, описывающую адрес. Третий параметр – размер этой структуры. Данная структура определена следующим образом:

struct sockaddr {

u_short sa_family; /* address family */

char sa_data[14]; /* up to 14 bytes of direct address */

};

Поле sa_family определяет тип адреса. Для internet адресов это значение установлено в AF_INET. sa_data содержит адрес.

Приложения могут легко обращаться к различным компонентам адреса через структуру sockaddr_in (вместо использования sockaddr). Определение этой структуры следующее:

struct sockaddr_in {

short sin_family;

u_short sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

Поле sin_port - 16-разрядный номер порта, который будет ассоциирован с данным сокетом. sin_addr - 32-разрядный IP адрес. sin_zero – заглушка, используется для того, чтобы размеры структур sockaddr_in и sockaddr совпадали.

Поле sin_addr определено как структура типа

struct in_addr {

union {

struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

struct { u_short s_w1,s_w2; } S_un_w;

u_long S_addr;

} S_un;

Если установить S_addr = htonl(INADDR_ANY) , то будет использован IP-адрес по умолчанию для данного компьютера.

Поиск адреса хост-компьютера

Чтобы заполнить поле sin_addr sockaddr_in, мы должны сначала получить 32-разрядный адрес хоста. Получить адрес, когда символическое имя хоста известно, можно используя функцию gethostbyname().

struct hostent FAR * gethostbyname(const char FAR * name);

При вызове gethostbyname(), приложение в качестве параметра передает символическое имя хоста и получает указатель на структуру hostent.

Структура hostent определяется следующим образом:

struct hostent {

char FAR * h_name; /* official name of host */

char FAR * FAR * h_aliases; /* alias list */

short h_addrtype; /* host address type */

short h_length; /* length of address */

char FAR * FAR * h_addr_list; /* list of addresses */

#define h_addr h_addr_list[0] /* address, for backward compat */

};

Эта структура необходима, потому что имя хоста может быть связано с несколькими адресами. В большинстве случаев, приложения только берут первый адрес в h_addr_list. Для того чтобы упростить этот процесс, символ h_addr определен как h_addr_list [0].

Если пользователь желает для передачи в функцию bind() использовать вместо этого числовой формат адреса , например “198.0.0.1”, он должен использовать функцию inet_addr() (например inet_addr(“198.0.0.1”)), чтобы преобразовать строку в 32-разрядное значение адреса.

Если Вы собираетесь исполнять приложение на компьютере. В котором установлено и более сетевых плат, то нужно указать конкретно один из сетевых адресов. Можно упростить указание адреса за счет использования адреса по умолчанию, сконфигурированного для компьютера, на котором исполняется приложение. Для этого используется константа INADDR_ANY в поле S_addr.

Функция gethostbyname() является блокирующей и заставляет ваше приложение остановиться и ждать до тех пор, пока функция не выполнит свою задачу. Для этой функции реализована асинхронная версия в виде расширения Windows (любая функция WinSock API, которая начинается с WSA, является расширением Windows к BSD socket API).

HANDLE WSAAsyncGetHostByName(HWND hWnd, u_int wMsg,

const char FAR * name, char FAR * buf, int buflen);

Функции WSAAsyncGetHostByName() передается пять параметров. Первым параметром является дескриптор окна, которое служит для приема сообщения о событии, информирующем вас о том, что извлечен адрес host-компьютера. Второй параметр - это сообщение о событии, которое посылается в окно, указанное в первом параметре. Третий параметр представляет собой имя другого компьютера. Четвертым параметром является буфер, в который нужно поместить адрес, а последний параметр - это длина буфера, переданного в четвертом параметре. Буфер передается в виде указателя , но в действительности является указателем на структуру записи hostent.

Несоответствие внутреннего представления данных

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

Для упрощения этого преобразования имеется следующий набор функций: htonl, htons, ntohl, и ntohs. Эти функции служат для преобразования из внутреннего представления в сетевой формат длинных и коротких значений, а последние две функции наоборот.

Ожидание запросов на открытие соединения

После создания сокета и привязки его к адресу необходимо установить соединение с клиентом. Для этого используется функция

int listen (SOCKET s, int backlog);

Вызов этой функции инициирует ожидание запроса клиента на открытие соединения. Второй параметр представляет собой максимальную длину очереди входящих запросов на установление соединения.

Открытие соединения

При получении запроса клиента открытие соединения выполняется функцией

SOCKET accept (SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);

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

Если соединение открывается успешно, то функция accept() возвращает дескриптор нового сокета, который будет использоваться для управления новым соединением. Если произошла ошибка, функция accept() возвращает код INVALID_SOCKET, и для получения более подробной информации об ошибке необходимо вызвать функцию WSAGetLastError().

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

Запрос на открытие соединения