Шрифт:
Интервал:
Закладка:
# define __SОСKADDR_ALLTYPES
__SOCKADDR_ONETYPE (sockaddr)
__SOCKADDR_ONETYPE (sockaddr_at)
__SОСKADDR_ONETYPE (sockaddr_ax25)
__SOCKADDR_ONETYPE (sockaddr_dl)
__SOCKADDR_ONETYPE (sockaddr_eon)
__SОСKADDR_ONETYPE (sockaddr_in)
__SOCKADDR_ONETYPE (sockaddr_in6)
__SОСKADDR_ONETYPE (sockaddr_inarp)
__SOCKADDR_ONETYРЕ (sockaddr_ipx)
__SОСKADDR_ONETYPE (sockaddr_iso)
__SОСKADDR_ONETYPE (sockaddr_ns)
__SOCKADDR_ONETYPE (sockaddr_un)
__SOCKADDR_ONETYPE (sockaddr_x25)
Мы программируем для сети TCP/IP, поэтому будем использовать структуру sockaddr_in (для IPv4) или sockaddr_in6 (для IPv6).
Последний аргумент — это длина выбранной нами структуры (sockaddr_in) в байтах.
Структура sockaddr_in определена в файле in.h так:
struct sockaddr_in {
__SОСKADDR_COMMON(sin_);
in_port_t sin_port; /* Номер порта */
struct in_addr sin_addr; /* IP-адрес */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SОСKADDR_COMMON_SIZE -
sizeof(in_port_t) - sizeof (struct in_addr)];
};
/* для IPv6. */
struct sockaddr_in6 {
__SОСKADDR_COMMON(sin6_);
in_port_t sin6_port; /* Порт транспортного уровня */
uint32_t sin6_flowinfo; /* Информация потока IPv6 */
struct in6_addr sin6_addr; /* адрес IPv6 */
uint32_t sin6_scope_id; /* IPv6-идентификатор */
};
Поля структуры sockaddr_in означают следующее:
♦ sin_ — набор используемых протоколов. Так как мы используем TCP/IP, данное поле должно содержать значение AF_INET;
♦ sin_port — номер порта;
♦ sin_addr — структура, определяющая адрес узла;
♦ sin_zero — обычно не используется.
Структура struct in_addr, определяющая адрес узла, также описана в файле in.h:
struct in_addr {
in_addr_t s_addr;
};
Обычно поле s_addr должно принимать значение INADDR_ANY — сейчас поясню почему. Структура sockaddr_in должна быть заполнена ДО вызова функции bind(). Если поле sin_addr.s_addr принимает значение INADDR_ANY, то функция bind() автоматически привяжет к сокету адрес локального компьютера и нам не нужно будет указывать его явно — так наша программа будет универсальной.
Функция bind() возвращает 0 в случае успеха, и -1, если произошла ошибка. Вот небольшой пример использования этой функции:
struct sockaddr_in client;
...
client.sin_family = AF_INET;
client.sin_addr.s_addr = INADDR_ANY;
client.sin_port = 1235;
bind(sock, (struct sockaddr *)&client, sizeof(client));
27.3.3. Установление связи с удаленным компьютером
Устанавливать связь можно как на стороне сервера, так и на стороне клиента. На стороне клиента используется только один вызов — connect(), который «спрашивает» у сервера: «Могу ли я подключиться?», то есть передает запрос на установление соединения. На сервере используются функции:
♦ listen() — ожидание клиента;
♦ accept() — подтверждение запроса клиента на установление соединения.
Сервер должен постоянно прослушивать сокет — ожидать новых клиентов. Как только новый клиент посылает запрос на установление соединения, сервер может либо разрешить ему подключиться (connect), либо запретить (например, если сервер уже обслуживает другого клиента).
Функция listen()Вызов listen() «заставляет» программу-сервер работать в режиме ожидания запроса на соединение от клиента. Прототип этой функции следующий:
#include <sys/socket.h>
extern int listen (int __fd, int __n) __THROW;
Первый параметр — это дескриптор сокета, а второй — максимальное количество запросов на установление связи (другими словами, максимальное количество клиентов).
Как и функция bind(), функция listen() в случае успеха возвращает 0. Пример вызова функции:
if (listen (sock1, 3) != 0) {
printf("Ошибка при вызове listen(sock1, 3)n");
exit(1);
}
Функция connect()Используется программой-клиентом для отправки запроса на подключение к серверу. Прототип функции следующий:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
extern int connect (int __fd, struct sockaddr_in *addr,
socklen_t __len) __THROW;
Первый параметр — это дескриптор сокета, созданного функцией socket() и привязанного функцией bind(). Привязку сокета функцией bind() выполнять не обязательно: если сокет не был привязан до вызова connect(), привязка будет выполнена автоматически.
Второй параметр — это указатель на структуру типа sockaddr_in, содержащую информацию о сервере: его IP-адрес, номер порта, а также семейство протоколов.
Последний параметр — это размер структуры sockaddr_in в байтах. В случае успеха функция возвращает 0, а в случае ошибки —1.
Вот пример использования вызова connect:
struct sockaddr_in server;
struct hostent *h;
...
// определяем IP-адрес сервера
h = gethostbyname("server.domain.ru");
memcpy((char*)&server.sin_addr, h->h_addr, h->h_length);
// Определяем порт сервера
server.sin_port = 1234;
// Определяем семейство протоколов
server.sin_family = AF_INET;
// Вызов функции connect()
connect(sock, &server, sizeof(server));
Если вы используете режим без установления соединения (SOCK_DGRAM), вызов connect() необязателен.
Функция accept()Если максимальное число клиентов не превышено, сервер может принять запрос клиента. Для этого используется функция accept(). Данная функция используется только при работе в режиме с установлением соединения. Прототип функции следующий:
#include <sys/socket.h>
#include <netinet/in.h>
extern int accept(int __fd, struct sockaddr_in *addr,
socklen_t *__restrict __len) __THROW;
Первый параметр — это дескриптор сокета, второй — указатель на структуру, где можно разместить адрес клиента, причем данную структуру инициализировать не нужно. Последний параметр — размер структуры, указанной во втором параметре.
Системный вызов accept() работает так. Сначала он извлекает из очереди listen() запрос на соединение и создает новый сокет, через который будет производиться обмен данными с клиентом, например:
// получаем сокет клиента
sock2 = accept(sock1, &client, &ans_len);
// передаем клиенту информацию
write(sock2, MSG_TO_SEND, sizeof(MSG_TO_SEND));
Если вызов accept() завершился успехом, структура addr, задаваемая во втором параметре, будет содержать IP-адрес клиента.
Если очередь listen() пуста, то наш сервер будет ожидать появления нового клиента. В случае ошибки функция accept() возвращает отрицательное значение.
27.3.4. Функция gethostbyname()
Пользователям обычно удобнее указать символьное имя сервера, чем его IP-адрес. Для разрешения имени служит функция gethostbyname(). Вот ее прототип:
#include <netinet/in.h>
#include <netdb.h>
struct hostent *gethostbyname(char *name);
Данная функция возвращает указатель на структуру типа hostent, содержащую следующие поля:
♦ char *h_name — доменное имя узла;
♦ char **h_aliases — псевдонимы узла, если таковые определены;
♦ char *h_addr — IP-адрес узла;
♦ int h_addrtype — набор используемых протоколов (в нашем случае — AF_INET);
♦ int h_length — длина адреса узла.
Примеры использования функции:
struct hostent *h;
h = gethostbyname(*argv);
if (h==NULL) {
printf("Невозможно разрешить имя: `%s`n", *argv);
exit(1);
}
// Выводим IP-адрес. Вывод в виде: имя -> адрес
printf("%s -> %s n", *argv,
inet_ntoa(*((struct in_addr *)h->h_addr_list[0])));
Узнать свой собственный адрес можно с помощью функции getsockname():
extern int getsockname(int __fd, __SOCKADDR_ARG __addr,
socklen_t *__restrict __len) __THROW;
Ей нужно передать три параметра — дескриптор сокета, адрес структуры, которая будет содержать информацию о нашем узле (его адрес). Третий параметр будет содержать длину адресной структуры.
27.3.5. Функции сетевого ввода/вывода
После успешного установления соединения можно начать обмен данными. Для отправки и получения данных можно использовать обыкновенные функции для работы с файлами — read() и write(), только вместо дескриптора файла нужно указывать дескриптор сокета. Однако рекомендуется использовать системные вызовы send() и recv(), которые предназначены именно для работы с сокетами. Эти системные вызовы будут рассмотрены ниже.
Если вы работаете в режиме без установления соединения, вам нужно использовать функции sendto() и recvfrom(). Первая функция отправляет данные, а вторая — принимает. Функция sendto() вместе с данными позволяет указать адрес получателя, a recvfrom() возвращает не только полученные данные, но и адрес отправителя.
- Операционная система UNIX - Андрей Робачевский - Программное обеспечение
- Разработка приложений в среде Linux. Второе издание - Майкл Джонсон - Программное обеспечение
- Искусство программирования для Unix - Эрик Реймонд - Программное обеспечение
- Linux - Алексей Стахнов - Программное обеспечение
- Fedora 8 Руководство пользователя - Денис Колисниченко - Программное обеспечение
- Недокументированные и малоизвестные возможности Windows XP - Роман Клименко - Программное обеспечение
- Изучаем Windows Vista. Начали! - Дмитрий Донцов - Программное обеспечение
- Windows Vista - Виталий Леонтьев - Программное обеспечение
- Архитектура операционной системы UNIX - Морис Бах - Программное обеспечение
- Windows Vista. Трюки и эффекты - Юрий Зозуля - Программное обеспечение