IPPORT_USERRESERVED = 5000
};
Нам понадобятся сразу два сокета: первый — это сокет сервера, а через второй сокет мы будем производить обмен данными с клиентом.
int sock1, sock2;
Следующие переменные: ans_len используется для хранения размера передаваемой клиентом информации — фактического размера структуры struct sockaddr_in, а total — это счетчик числа клиентов, используемый для вывода порядкового номера клиента.
Переменная buffer размера BUF_SIZE — это наш буфер для обмена информацией. Нам нужны две структуры типа sockaddr_in — одна для сервера (sin) и одна для клиента (client).
В строке
sock1 = socket(AF_INET, SOСK_STREAM, 0);
мы создаем наш, «серверный», сокет: набор протоколов — TCP/IP, режим — с установлением соединения.
Затем мы инициализируем структуру sin:
memset((char *)&sin, ' ', sizeof(sin));
sin.sin_family = AF_INET; // TCP/IP
sin.sin_addr.s_addr = INADDR_ANY; // можем работать на
// любом адресе
sin.sin_port = SERVER_PORT; // указываем порт (1234)
После создания сокета и инициализации структуры sin, нужно связать наш сокет с адресом и портом сервера:
bind(sock1, (struct sockaddr *)&sin, sizeof(sin));
Оператор listen(sock1, 3) означает, что мы будем прослушивать сокет sock1 (порт 1234) и максимальное число клиентов не должно превышать 3.
Как и любой нормальный сервер, мы должны работать в бесконечном цикле, постоянно обрабатывая запросы клиентов. В бесконечном цикле мы:
1. получаем размер структуры client
ans_len = sizeof(client);
2. создаем сокет sock2, через который будем обмениваться данными с клиентом. Если в очереди listen нет клиентов, мы переходим в состояние ожидания
sock2 = accept(sock1, &client, &ans_len);
3. как только подключится клиент, мы отправим ему сообщение MSG_TO_SEND
write(sock2, MSG_TO_SEND, sizeof(MSG_TO_SEND));
4. увеличиваем счетчик клиентов
total+=1;
5. получаем размер прочитанных данных, сами данные записываются в буфер buffer
ans_len = read(sock2, buffer, BUF_SIZE);
6. выводим прочитанные данные на стандартный вывод
write(1, buffer, ans_len);
7. завершаем сеанс связи
shutdown(sock2, 0);
8. закрываем сокет
close(sock2);
Конечно, любой нормальный сервер при поступлении определенных сигналов, например, SIG_HUP, должен корректно перезапуститься или вообще завершить работу. Наш сервер этого не делает — обработку сигналов, я надеюсь, вы можете добавить сами.
Теперь мы можем откомпилировать нашу программу:
$ gcc -о server server.c
Запускаем:
./server
Программа перешла в состояние ожидания новых клиентов.
Программа-клиент несколько проще, чем сервер. Вот ее листинг:
Листинг 27.4. Программа-клиент
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <memory.h>
#include <stdio.h>
#define SERVER_HOST "localhost"
#define SERVER_PORT 1234
#define CLIENT_PORT 1235
#define MSG "Denisn"
main() {
int sock;
int ans_len;
int BUF_SIZE = 64;
char buffer[BUF_SIZE];
struct hostent *h;
struct sockaddr_in client, server;
sock = socket(AF_INET, SOCK_STREAM, 0);
memset((char *)&client, ' ', sizeof(client));
client.sin_family = AF_INET;
client.sin_addr.s_addr = INADDR_ANY;
client.sin_port = CLIENT_PORT;
bind(sock, (struct sockaddr *)&client, sizeof(client));
memset((char *)&client, ' ', sizeof(server));
h = gethostbyname(SERVER_HOST);
server.sin_family = AF_INET;
memcpy((char *)&server.sin_addr.h->h_addr, h->h_length);
server.sin_port = SERVER_PORT;
connect(sock, &server, sizeof(server));
ans_len = recv(sock, buffer, BUF_SIZE, 0);
write(1, buffer, ans_len);
send(sock, MSG, sizeof(MSG), 0);
close(sock);
exit(0);
}
Константа MSG — это сообщение, которое будет передано серверу. Как и в случае с сервером, нам понадобятся две структуры типа sockaddr_in:
struct hostent *h;
struct sockaddr_in client, server;
Структура типа hostent нам нужна для получения адреса сервера.
Создаем сокет, заполняем информацию о клиенте и связываем сокет:
sock = socket(AF_INET, SOCK_STREAM, 0);
memset((char *)&client, ' ' , sizeof(client));
client.sin_family = AF_INET;
client.sin_addr.s_addr = INADDR_ANY;
client.sin_port = CLIENT_PORT;
bind(sock, (struct sockaddr *)&client, sizeof(client));
Перед подключением к серверу нужно определить его IP-адрес:
h = gethostbyname(SERVER_HOST);
Подключаемся к серверу:
server.sin_family = AF_INET; // набор протоколов
memcpy((char *)&server.sin_addr, h->h_addr, h->h_length);
// задаем адрес сервера
server.sin_port = SERVER_PORT; // указываем порт сервера
connect(sock, &server, sizeof(server));
После подключения к серверу принимаем его запрос, выводим на стандартный вывод, отправляем серверу свое сообщение и закрываем сокет:
ans_len = recv(sock, buffer, BUF_SIZE, 0);
write(1, buffer, ans_len);
send(sock, MSG, sizeof(MSG), 0);
close(sock);
27.3.9. Установка опций сокета
Поскольку мы используем набор протоколов AF_INET, то в этом пункте будем рассматривать только те опции сокетов, которые относятся к этому набору. Для работы с опциями сокета используются две функции:
♦ getsockopt() — получение опций сокета;
♦ setsockopt() — установка опций сокета.
Прототипы этих функций выглядят так:
#include <sys/socket.h>
int getsockopt(int sd, int level, int option_name,
void *restrict option_value, socklen_t *restrict option_len);
int setsockopt(int sd, int level, int option_name,
const void *option_value, socklen_t option_len);
Первый параметр, sd, — это дескриптор сокета. Второй параметр — уровень доступа (существует только один уровень — SOL_SOCKET). Следующий параметр, option_name, — это название опции, значение которой вы хотите изменить (см. таблицу 27.10). Последние два параметра — это значение опции и его размер.
Наиболее часто используемые опции сокетов Таблица 27.10
Название опции Описание SO_DEBUG Включить/выключить (1/0) запись отладочной информации для сокета SO_BROADCAST Включить/выключить (1/0) отправку широковещательных сообщений SO_REUSEADDR Опция разрешает/запрещает использование локальных адресов SO_KEEPALIVE Сохраняет неактивные соединения "в живых" путем посылки сообщений. Если данный сокет не отвечает на сообщения, соединение будет разорвано, а процессу, который осуществлял запись в сокет, будет послан сигнал SIGPIPE. Для включения KEEPALIVE нужно установить значение 1, для выключения — 0 SO_SNDBUF Устанавливает размер буфера отправки, значение целого типа SO_RCVBUF Устанавливает размер буфера приема, значение целого типа SO_SNDTIMEO Установка таймаута для отправки сообщений. По умолчанию таймаут равен 0, то есть его вообще нет. Нужно передать значение типа struct timeval SO_RCVTIMEO Установка таймаута для приема сообщений. По умолчанию таймаут равен 0, то есть его вообще нет. Нужно передать значение типа struct timeval TCP_NODELAY Отключить (1) механизм буферизации сообщений, то есть они будут отправляться сразу, без задержки. Для включения механизма буферизации нужно указать значение 0 TCP_MAXSEG Установить максимальный сегмент данных. Значение целого типа TCP_NOPUSH Не использовать проталкивание (1) TCP_NOOPT Не использовать опции TCP (1). Для использования опций передайте значение 0
В случае успешной установки параметра функция setsockopt() возвращает 0; в случае ошибки возвращается -1, а переменная errno устанавливается следующим образом:
♦ EBADF — неверный дескриптор сокета:
♦ ENOTSOCK — указанный дескриптор является файлом, а не сокетом;
♦ EFAULT — нет доступа к адресу, на который указывает указатель optval, то есть данный адрес находится за пределами видимости приложения.
Функция getsockopt() возвращает значение параметра. Кроме вышеперечисленных параметров, функция getsockopt() может использовать следующие параметры: