информацию о нашем узле (его адрес). Третий параметр будет содержать длину адресной структуры.
27.3.5. Функции сетевого ввода/вывода
После успешного установления соединения можно начать обмен данными. Для отправки и получения данных можно использовать обыкновенные функции для работы с файлами — read() и write(), только вместо дескриптора файла нужно указывать дескриптор сокета. Однако рекомендуется использовать системные вызовы send() и recv() , которые предназначены именно для работы с сокетами. Эти системные вызовы будут рассмотрены ниже.
Если вы работаете в режиме без установления соединения, вам нужно использовать функции sendto() и recvfrom(). Первая функция отправляет данные, а вторая — принимает. Функция sendto() вместе с данными позволяет указать адрес получателя, a recvfrom() возвращает не только полученные данные, но и адрес отправителя.
Для отправления данных используется функция send():
#include <sys/types.h>
#include <sys/socket.h>
extern ssize_t send (int __fd, __const void *__buf,
size_t __n, int __flags) __THROW;
Первый параметр — дескриптор сокета, второй — указатель на область памяти, которая содержит передаваемые данные. Третий параметр — это размер передаваемых данных в байтах. Последний параметр позволяет определить поведение функции send(): если он равен 0, то вызов send() полностью аналогичен вызову write().
Нужно отметить особенность работы этой функции; если буфер сокета __fd переполнен, функция переводит программу в состояние ожидания освобождения буфера. Такое может случиться, если узел-приемник по каким-то причинам не успевает принять данные.
Функция возвращает число байтов отправленных данных или -1 в случае ошибки.
Для приема данных используется функция recv():
#include <sys/types.h>
#include <sys/socket.h>
extern ssize_t recv(int __fd, void *__buf, size_t __n,
int __flags) __THROW;
Первый параметр, как обычно, задает дескриптор сокета. В случае успешного приема данных они будут размешены в буфере __buf — второй параметр функции recv(). Третий параметр задает размер области, на которую указывает второй параметр. Если четвертый параметр (флаги) принимает значение 0, то вызов recv() аналогичен вызову read() . Четвертый параметр может принимать следующие значения:
♦ MSG_PEEK — прочитанные данные не удаляются. Следующий вызов функции recvfrom() опять возвратит эти данные.
♦ MSG_WAITALL — процесс будет блокирован до получения всего запрошенного объема данных, а не до получения первого сообщения. Только для сокетов SOCK_STREAM!
Если через указанный сокет ничего нельзя принять, функция переводит программу в состояние ожидания — до появления данных в канале связи.
Функция возвращает количество принятых байтов или -1 в случае ошибки.
Функция sendto() позволяет отправить данные по протоколу UDP (без установления соединения), указав при этом узел-приемник:
extern ssize_t sendto(int __fd, __const void *__buf,
size_t __n, int __flags, __CONST_SOCKADDR_ARG __addr,
socklen_t __addr_len) __THROW;
Назначение первых четырех аргументов такое же, как и функции send(), а последние два аргумента задают структуру типа struct sockaddr_in, содержащую информацию об адресе узла-приемника, и размер этой структуры соответственно. Аргумент __addr
— это адрес структуры sockaddr_in, а не она сама!
Как и функция send(), функция sendto() возвращает количество байтов отправленных данных или -1, если произошла ошибка.
Функция recvfrom() позволяет получить данные по протоколу UDP:
extern ssize_t recvfrom(int __fd, void *__restrict __buf,
size_t __n, int __flags, __SOCKADDR_ARG __addr,
socklen_t *__restrict __addr_len) __THROW;
Назначение первых четыре аргументов такое же, как и у функции recv(). Предпоследний аргумент позволяет указать структуру, в которую будет записана информация об адресе узла-отправителя. Помните: нужно передать адрес структуры, а не саму структуру. Последний параметр задает длину этой структуры.
Функция возвращает количество принятых данных или -1 в случае ошибки. Проверить ошибку можно и по-другому: если структура адреса узла отправителя пуста (равна NULL), значит, произошла ошибка.
27.3.6. Завершение сеанса связи
Для закрытия сеанса связи можно использовать один из двух системных вызовов: close() или shutdown().
Системный вызов close() также используется для закрытия файлов. Вот прототип этой функции:
int close(int __fd);
Данной функции нужно передать всего один параметр — дескриптор сокета.
Однако вызов close() использовать не рекомендуется из-за специфики его работы: он закрывает сокет грубо, не дожидаясь завершения передачи данных. В результате использования close() вероятность повреждения принимаемых или передаваемых данных очень высока. В принципе, использовать close() можно на клиенте, но на сервере это недопустимо: сначала нужно использовать shutdown(), а потом уже close().
Вызов shutdown() используется для завершения сеанса связи, при этом еще не переданные данные будут переданы другой стороне. Прототип функции:
extern int shutdown(int __fd, int __how) __THROW;
Первый параметр — это дескриптор сокета, а второй может принимать одно из трех значений:
♦ SHUT_RD (или 0) — передать данные, которые еще не переданы, но их отправка уже началась, и больше не принимать данные для чтения.
♦ SHUT_WR (или 1) — передать данные и запретить прием данных через сокет.
♦ SHUT_RDWR (или 2) — передать данные и запретить вообще обмен через сокет — ни приема, ни передачи.
27.3.7. Программа-сервер