В листинге 53.2 приводится заголовочный файл, применяемый обеими программами.
Листинг 53.2. Заголовочный файл для программ us_xfr_sv.c и us_xfr_cl.c
sockets/us_xfr.h
#include
#include
#include "tlpi_hdr.h"
#define SV_SOCK_PATH "/tmp/us_xfr"
#define BUF_SIZE 100
sockets/us_xfr.h
На следующих страницах сначала будет представлен исходный код сервера и клиента, а затем мы обсудим подробности их реализации и рассмотрим пример их совместного использования.
Листинг 53.3. Простой сервер на основе потоковых сокетов домена UNIX
sockets/us_xfr_sv.c
#include "us_xfr.h"
#define BACKLOG 5
int
main(int argc, char *argv[])
{
struct sockaddr_un addr;
int sfd, cfd;
ssize_t numRead;
char buf[BUF_SIZE];
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1)
errExit("socket");
/* Формируем адрес сервера, привязываем к нему сокет и делаем этот сокет слушающим */
if (remove(SV_SOCK_PATH) == -1 && errno!= ENOENT)
errExit("remove-%s", SV_SOCK_PATH);
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) — 1);
if (bind(sfd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_un)) == -1)
errExit("bind");
if (listen(sfd, BACKLOG) == -1)
errExit("listen");
for (;;) { /* Последовательно обрабатываем клиентские соединения */
/* Принимаем соединение. Оно будет назначено новому сокету, 'cfd'; слушающий
сокет ('sfd') остается открытым и может принимать последующие соединения. */
cfd = accept(sfd, NULL, NULL);
if (cfd == -1)
errExit("accept");
/* Направляем данные подключенного сокета в стандартный вывод,
пока не обнаружим конец файла */
while ((numRead = read(cfd, buf, BUF_SIZE)) > 0)
if (write(STDOUT_FILENO, buf, numRead)!= numRead)
fatal("partial/failed write");
if (numRead == -1)
errExit("read");
if (close(cfd) == -1)
errMsg("close");
}
}
sockets/us_xfr_sv.c
Листинг 53.4. Простой клиент на основе потоковых сокетов домена UNIX
sockets/us_xfr_cl.c
#include "us_xfr.h"
int
main(int argc, char *argv[])
{
struct sockaddr_un addr;
int sfd;
ssize_t numRead;
char buf[BUF_SIZE];
sfd = socket(AF_UNIX, SOCK_STREAM, 0); /* Создаем клиентский сокет */
if (sfd == -1)
errExit("socket");
/* Формируем адрес сервера и выполняем соединение */
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) — 1);
if (connect(sfd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_un)) == -1)
errExit("connect");
/* Копируем в сокет стандартный ввод */
while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0)
if (write(sfd, buf, numRead)!= numRead)
fatal("partial/failed write");
if (numRead == -1)
errExit("read");
exit(EXIT_SUCCESS); /* При обнаружении конца файла сокет закрывается */
}
sockets/us_xfr_cl.c
Серверная программа, представленная в листинге 53.3, выполняет следующие действия:
• создает сокет;
• удаляет любой существующий файл с путем, к которому мы хотим привязать наш сокет;
• формирует структуру с адресом серверного сокета, привязывает сокет к этому адресу и делает его слушающим;
• входит в бесконечный цикл для обработки клиентских запросов. Каждая итерация цикла состоит из следующих шагов:
• принятие соединения и получение для него нового сокета, cfd;
• чтение всех данных из подключенного сокета и запись их в стандартный вывод;
• закрытие подключенного сокета cfd.
Работу сервера нужно завершать вручную (например, с помощью сигнала).
Клиентская программа из листинга 53.4 выполняет следующие действия:
• создает сокет;
• формирует структуру с адресом сервера и подключается к сокету по данному адресу;
• выполняет цикл, который копирует стандартный ввод в соединение сокета. При обнаружении конца файла в своем стандартном вводе клиент завершает работу, в результате чего его сокет закрывается. При этом сервер, считывая данные с сокета на другом конце соединения, видит символ конца файла.
Пример использования указанных программ показан в следующей сессии командной строки. Начнем с запуска сервера в фоновом режиме:
$ ./us_xfr_sv > b &
[1] 9866
$ ls — lF /tmp/us_xfr
srwxr-xr-x 1 mtk users 0 Jul 18 10:48 /tmp/us_xfr=
Теперь запустим клиентскую программу, предварительно создав тестовый файл, который будет применяться в качестве ее ввода:
$ cat *.c > a
$ ./us_xfr_cl < a
На данном этапе дочерний процесс уже завершился. Завершим работу сервера и проверим, совпадает ли его вывод с вводом клиента:
$ kill %1
[1]+ Terminated./us_xfr_sv >b
$ diff a b
$