Рассылка закрыта
При закрытии подписчики были переданы в рассылку "LinuxCenter News Channel: новости Linux" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Linux Gazette на русском
Здравствуйте!
Сегодня вашему вниманию предлагается очередной опус Жаовая, известного стебка из Наньдзиня:)
По форме он "травит", по сути -- это поучительно и полезно:) Так что читайте.
Сергей Скороходов suralis-s@mtu-net.ru
Игры с сетевыми котами: изобретем /usr/bin/yes еще раз
Автор: (C) Жаовай [zhaoway]
Перевод: (C) Сергей Скороходов
Кошка Нет и и кошка Да [Netcat and Yescat]
Первая (но не главная) задача этой статьи: познакомить вас с прелестной
сетевой "тулзой": /usr/bin/netcat, которую
можно без труда найти в одноименном пакете Debian GNU/Linux (упражнение:
выполните apt-get install netcat -- и готово!). Автор
программы, пожелавший остаться неизветстным, снабдил ее хорошо
написанной документацией, на основе которой мои коллеги-разработчики
Debian сделали прекрасно отформатированную страницу руководства
Unix. Читать эту документацию -- одно удовольствие. Благородному
читателю наверняка придет в голову, что да, действительно есть
такие существа -- UNIX гуру -- живущие где-то там, в Дальнем Мире.
И они, исключительно из харерских побуждений, сохраняют
анонимность после написания столь превосходного программного
обеспечения. Только подлинные гуру Unix'а могут так поступить!
Раз уж документация по netcat так хороша, я ее здесь повторять не
буду. Однако, я советую всем прочесть ее, прежде чем приступать
к этой статье. Для нетерпеливых же сообщаю: netcat может перенаправлять
поток данных из стандартного ввода в TCP или UDP сокет, а из TCP
или UDP сокета -- в стандартный вывод. Точно так, как команда cat
перенаправляет из стандартного ввода [stdin] в стандартный вывод
[stdout]. По непроверенным данным, именно от этой утилиты произошло
название программы netcat.
Вторая, но зато основная задача этой статьи -- показать, сколь скучным и невежественным можте быть автор статей (вроде меня) при описании программы без графического интерфейса пользователя или интерактивной справки. Если я типа не могу "заграбастать" хотя бы пары скриншотов -- то я просто шизею!
А теперь, для целей, которые станут понятными далее, мы представим
пикантную программку /usr/bin/yes. Почти никто не
обращает на нее внимание. А она тихонько лежит там, в уголке /usr/bin
уже так давно, что едва ли кто-либо из нас, недавно пришедших в
мир Linux, хотя бы подозревает о ее присутствии в системе. Происхождение
этой программы покрыто тайной. А популярность у нее -- не меньше,
чем у /sbin/init! Что она делает? Давайте посмотрим:
zw@q ~ % yes y y y y y y y
Ну, разве не чудо? ;-) (Нажмите ctrl-C для того, чтобы
остановить поток 'y'-ов, иначе они будут вечно маршировать по экрану.)
Между прочем, программа может сказать и нет!
zw@q ~ % yes no no no no no no
В последующих разделах мы разработаем две вспомогательные утилиты,
с помощью которых мы сделаем свой вариант /usr/bin/yes,
естественно с помощью /usr/bin/netcat! В путь!
Hub и cable
Источником вдохновения при создании утилит hub (hub.c) и cable (cable.c), безусловно, явился netcat, пересылающий поток данных от сокета к стандартному выводу и со стандартного входа -- на сокет. Разве я забыл порекомендовать прочесть сопровождающую netcat документацию? ;-) Hub задуман, как сервер, а cable -- как клиент. Вместо того, чтобы перенаправлять данные между stdin, stdout и сокетами, hub и cable направляют и мультиплексируют данные от сокета на другие сокеты. Вот откуда взялись их имена. Они работают, как hub и cable в Ethernet. Взгляните на скриншот. О-о, скриншот! ;-)
zw@q ~ % ./hub Лаборатория Сетевых Колыбельных: hub (сервер) $Revision: 1.5 $ Copyright (C) 2001 zhaoway <zw@debian.org> Использование: hub [размер буфера] [номер порта tcp] [число портов в "хабе"] o размер буфера указывается в байтах. например 10240. o номер порта tcp должен быть не меньше 1024 для того, чтобы не требовались права root'а. o в хабе должно быть не менее двух портов. будьте счасливы. zw@q ~ %
Hub слушает порты TCP также, как работает Ethernet hub. Данные, поступающие с одного порта hub'а, будут направлятся на остальные его порты. С помощью netcat можно протестировать hub не прибегая к cable. Учтите: nc -- это сокращение для netcat.
- Запустите hub в консоли. Вот так:
ConA % ./hub 10240 10000 2 - Из консоли B подсоединитесь netcat'ом:
ConB % nc localhost 10000 - Из консоли C подключите еще один netcat:
ConC % nc localhost 10240 - Можете печатать текст в ConC, а читать в ConB и наоборот.
А теперь займемся cable:
zw@q ~ % ./cable Лаборатория Сетевых Леденцов: cable ( клиент) $Revision: 1.14 $ Copyright (C) 2001 zhaoway <zw@debian.org> Использовать: cable [размер буфера] [1-й ip] [1-й порт] [2-1 ip] [2-й порт] ... o размер буфера указывается в байтах. например 10240. o порты должны прослушиваться, иначе попытка соединения закончится неудачей. o должно быть указано по меньшей мере два набора ip:port. zw@q ~ %
Cable работает примерно как разделямый коаксиальный кабель Ethernet. Он перенаправляет и мультиплексирует данные между слушающими демонами сокетов. Давайте испытаем и его.
- Запускаем демон netcat в ConA:
ConA % nc -l -p 10000 - Запускаем другой демон netcat в ConB:
ConB % nc -l -p 10001 - Организуем cable:
ConC % ./cable 10240 127.0.0.1 10000 127.0.0.1 10001 - Теперь можно вводить текст в ConA, а читать в ConB и наоборот.
В разработке hub и cable применены любопытные приемы. Особенно
советую обратить внимание на вызов функции select().
А пока сосредоточимся на повторном измобретении /usr/bin/yes
;-).
Изобретаем колесо заново
Не так-то просто с помощью netcat, hub и cable изобрести /usr/bin/yes
еще раз. Могу намекнуть: поэтому мне и потребовалось устанавливать
размер буфера в аргументе командной строки. Тем не менее, приступим!
Основная идея такова. Сначала мы настроем трех-портовый hub, потом соединим два "хаба" с помощью "кабеля", а затем мы сможем использовать netcat для того, чтобы повторять любой символ в свободные порты "хаба". Как на диаграмме:
| кабель-cable
\|/ ,---------,
| | |
V V V
,--[ ]-------[ ]-------[ ]--.
| A B C |
| трехпортовый хаб-hub |
---------------------------'
Природа hub'а такова, что данные, посылаемые на порт А будут перенаправлены на порты B и C, а так как порты B и C соеденены кабелем, данные, поступившие из "хаба" отправятся равнехонько назад и будут мультеплексированы и направлены в порт А, циркулируя в петле кабеля до бесконечности. В конце концов порт А получит бесконечное число копий изначально введенных данных.
Соберем устройство.
- В ConA запускаем трехпортовый "хаб":
ConA % ./hub 10240 10000 3 - В ConB замыкаем "кабель":
ConB % ./cable 10240 127.0.0.1 10000 127.0.0.1 10000
Теперь, когда мы завершили сборочные работы, можно с помощью netcat
наконец-то завершить наше повторное изобретение /usr/bin/yes.
ConC % echo "y" | nc localhost 10000 y y y y y y
Хитрое упражнение для читателя: что случится, если мы изменим размер буфера (и для hub, и для cable) с 10240 байт до 1 байта? Можете попробовать сами.
Так что не скучайте и удачи!
Код примеров
Клиент cable
/* -*- C -*-
* Лаборатория Сетевых Колыбельных: cable (клиент)
*
* Copyright (C) 2001 zhaoway <zw@debian.org>
*
* $Id: cable.c,v 1.15 2001/12/14 01:33:55 zw Exp $
*
* Комприляция: gcc -Wall -g -o cable cable.c
*
* Pri perevode ispol'zovalas' kodirovka KOI8-R
*/
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/socket.h>
void banner(void)
{
printf("Лаборатория Сетевых Колыбельных: cable (клиент) $Revision: 1.14 $\n"
"Copyright (C) 2001 zhaoway <zw@debian.org>\n\n");
}
void usage(void)
{
banner();
printf("Использование: cable [размер буфера] [1-й ip] [1-й порт] [2-й ip] [2-й порт] ..\n\n"
"o размер буфера указывается в байтах. например 10240.\n"
"o порты должны прослушиваться, иначе попытка соединения закончится неудачей.\n"
"o должно быть указано по меньшей мере два набора ip:port.\n");
}
/* При неудаче возвращает -1. */
int make_socket(struct sockaddr_in *servaddr)
{
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(sockfd, (struct sockaddr *) servaddr, sizeof(struct sockaddr_in)) == -1)
{
perror("connect");
return -1;
}
return sockfd;
}
/* Возвращает число открытых портов, в случае неудачи оно < 2. */
int cmdline(int argc, char *argv[], int *sockfd[], int *maxline)
{
struct sockaddr_in *servaddr, tmpaddr;
int ports = 2, i;
/* У нас должно быть по меньшей мере два соединения. */
servaddr = malloc(ports * sizeof(struct sockaddr_in));
if (servaddr == NULL)
{
fprintf(stderr, "не хватило памяти!\n");
return 1;
}
memset(servaddr, 0, sizeof(servaddr));
for (i = 0; i < ports; i++) servaddr[i].sin_family = AF_INET;
if (argc < 6
|| (*maxline = strtol(argv[1], NULL, 10)) == 0
|| (inet_pton(AF_INET, argv[2], &servaddr[0].sin_addr)) <= 0
|| (servaddr[0].sin_port = htons(strtol(argv[3], NULL, 10))) == 0
|| (inet_pton(AF_INET, argv[4], &servaddr[1].sin_addr)) <= 0
|| (servaddr[1].sin_port = htons(strtol(argv[5], NULL, 10))) == 0)
{
usage();
return 0;
}
banner();
for ( ; ; )
{
if ((argc -= 2) < 6) break;
memset(&tmpaddr, 0, sizeof(struct sockaddr_in));
if ((inet_pton(AF_INET, argv[ports * 2 + 2], &tmpaddr.sin_addr)) <= 0)
break;
if ((tmpaddr.sin_port = htons(strtol(argv[ports * 2 + 3], NULL, 10))) == 0)
break;
servaddr = realloc(servaddr, (ports + 1) * sizeof(struct sockaddr_in));
servaddr[ports].sin_addr = tmpaddr.sin_addr;
servaddr[ports].sin_port = tmpaddr.sin_port;
ports++;
}
*sockfd = malloc(ports * sizeof(int));
if (*sockfd == NULL)
{
fprintf(stderr, "не хватило памяти!\n");
return 1;
}
for (i = 0; i < ports; i++)
{
if (((*sockfd)[i] = make_socket(&servaddr[i])) == -1)
{
if (i >= --ports) break;
servaddr[i] = servaddr[ports];
i--; /* retry */
}
}
return ports;
}
int main(int argc, char *argv[])
{
int *sockfd, ports, size, maxline, i, j;
char *buff;
fd_set rset, rset_memo, wset, wset_memo;
struct timeval nowait = { 0, 0 };
if ((ports = cmdline(argc, argv, &sockfd, &maxline)) < 2) return 1;
buff = (char *) malloc(maxline * sizeof(char));
if (buff == NULL)
{
fprintf(stderr, "не хватило памяти!\n");
return 1;
}
FD_ZERO(&rset_memo);
FD_ZERO(&wset_memo);
for (i = 0; i < ports; i++)
{
FD_SET(sockfd[i], &rset_memo);
FD_SET(sockfd[i], &wset_memo);
}
/* Ignore this to receive EPIPE. */
signal(SIGPIPE, SIG_IGN);
/* Main loop. */
for ( ; ; )
{
if (ports < 2)
{
fprintf(stderr,
"кабель поврежден и только %i соединение(я) по прежнему отркрыто(ы)\n", ports);
return 1;
}
/* Готовимся читать. */
rset = rset_memo;
/* С какого-нибудь порта посылаются данные? */
if ((select(FD_SETSIZE, &rset, NULL, NULL, &nowait)) <= 0) continue;
/* Ищем порт, из которого можно читать. */
for (i = 0; i < ports; i++)
{
/* Можно ли что-нибудь прочесть из этого порта? */
if (! FD_ISSET(sockfd[i], &rset)) continue;
/* А если не получится, то... */
else if ((size = recv(sockfd[i], buff, maxline, 0)) == -1)
{
perror("recv err");
return errno;
}
/* А если мы прочли в точности ничего? */
else if (size == 0) continue;
/* Что-то прочли, приготовимся к записи. */
wset = wset_memo;
/* А если писать мы не можем? */
if ((select(FD_SETSIZE, NULL, &wset, NULL, &nowait)) <= 0)
{
fprintf(stderr, "Кабель \"поломатый\", с записью ничего не вышло!\n");
return 2;
}
/* Write. */
for (j = 0; j < ports; j++)
{
/* Не пишите ответов в порт, посылающий данные. */
if (j != i && FD_ISSET(sockfd[j], &wset))
{
/* Проверим возможность записи. */
if ((send(sockfd[j], buff, size, 0)) == -1)
{
if (errno == EPIPE)
{
FD_CLR(sockfd[j], &wset);
FD_CLR(sockfd[j], &rset);
close(sockfd[j]);
sockfd[j] = sockfd[--ports];
}
else
{
perror("send err");
return errno;
}
}
}
}
} /* Цикл, ищущий порт, из которого можно читать. */
} /* Главный цикл. */
}
Сервер hub
/* -*- C -*-
* Лаборатория Сетевых Колыбельных: cable (клиент)
*
* Copyright (C) 2001 zhaoway <zw@debian.org>
*
* $Id: hub.c,v 1.6 2001/12/14 01:34:41 zw Exp $
*
* Компиляция: gcc -Wall -g -o hub hub.c
*
* Pri perevode ispol'zovalas' kodirovka KOI8-R
*/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
void banner(void)
{
printf("Лаборатория Сетевых Колыбельных: hub (сервер) $Revision: 1.6 $\n"
"Copyright(C) 2001 zhaoway <zw@debian.org>\n\n");
}
void usage(void)
{
banner();
printf("Использование: hub [размер буфера] [номер порта tcp] [число портов в хабе]\n\n"
"o размер буфера указывается в байтах. например 10240.\n"
"o номер порта tcp должен быть не меньше 1024 для того, чтобы не требовались права root\'а.\n"
"o в хабе должно быть не менее двух портов. будьте счасливы.\n");
}
/* При неудаче возвращает -1. */
int make_socket(short int port)
{
int listenfd, val;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if ((bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) == -1)
{
perror("bind err");
return -1;
}
listen(listenfd, 1);
/* Установим сокет неблокирующим. */
val = fcntl(listenfd, F_GETFL);
fcntl(listenfd, F_SETFL, val | O_NONBLOCK);
return listenfd;
}
int main(int argc, char *argv[])
{
int listenfd, *connfd, maxline, size, port, ports, i, j;
fd_set rset, rset_memo, wset, wset_memo;
char *buff;
struct timeval nowait = { 0, 0 };
if (argc != 4
|| (maxline = strtol(argv[1], NULL, 10)) == 0
|| (port = strtol(argv[2], NULL, 10)) == 0
|| (ports = strtol(argv[3], NULL, 10)) < 2)
{
usage();
return(0);
}
banner();
buff = (char *) malloc(maxline * sizeof(char));
if (buff == NULL)
{
fprintf(stderr, "не хватило памяти!\n");
return 1;
}
if ((listenfd = make_socket(port)) == -1)
{
usage();
return -1;
}
printf("Hub слушает на порте TCP %i\n", port);
connfd = malloc(ports * sizeof(int));
if (connfd == NULL)
{
fprintf(stderr, "не хватило памяти!\n");
return 1;
}
for (i = 0; i < ports; i++)
{
connfd[i] = -1;
}
FD_ZERO(&rset_memo);
FD_ZERO(&wset_memo);
/* Игнорируем получение EPIPE. */
signal(SIGPIPE, SIG_IGN);
/* Главный цикл. */
for ( ; ; )
{
for (i = 0; i < ports; i++)
{
if (connfd[i] == -1)
{
connfd[i] = accept(listenfd, NULL, NULL);
if (connfd[i] != -1)
{
FD_SET(connfd[i], &rset_memo);
FD_SET(connfd[i], &wset_memo);
}
}
}
/* Подготовка к чтению. */
rset = rset_memo;
/* Нечего читать? */
if ((select(FD_SETSIZE, &rset, NULL, NULL, &nowait)) <= 0) continue;
/* Ищем порт, из которого можно что-нибудь прочесть. */
for (i = 0; i < ports; i++)
{
/* Порт подсоединен и из него можно читать? */
if (connfd[i] == -1 || ! FD_ISSET(connfd[i], &rset)) continue;
/* Проблемы с чтением? */
if ((size = recv(connfd[i], buff, maxline, 0)) == -1)
{
perror("recv err");
return errno;
}
/* На самом деле ничего не прочитано? */
if (size == 0) continue;
/* Готовимся писать. */
wset = wset_memo;
/* Нет порта, открытого на запись? */
if ((select(FD_SETSIZE, NULL, &wset, NULL, &nowait)) <= 0) continue;
/* В цикле ищем любой пригодный для записи порт. */
for (j = 0; j < ports; j++)
{
/* Не пишите в порт, если из него читается. */
if (j == i || connfd[j] == -1 || ! FD_ISSET(connfd[j], &wset))
continue;
if ((send(connfd[j], buff, size, 0)) == -1)
{
if (errno == EPIPE)
{
FD_CLR(connfd[j], &wset);
FD_CLR(connfd[j], &rset);
close(connfd[j]);
connfd[j] = -1;
}
else
{
perror("send err");
return errno;
}
}
}
}
} /* Главный цикл. */
}
Жаовай [Zhaoway]
Жаовай живет в Наньдзине, в Китае. Свое время он делит между прелестной подружкой, стареьнким "пнем" и чистой математикой. (Он занимается "высшематематическим самообразованием", так что если у вас завалялось несколько лишних, но драгоценных марок и/или книг по высшей математике, то не стесняйтесь послать ему копию). Еще он на добровольной основе участвует в проекте Debian GNU/Linux.
Copyright (C) 2002, zhaoway.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 74 of Linux Gazette, January 2002
Команда переводчиков:
Владимир Меренков, Александр Михайлов, Иван Песин, Сергей Скороходов,
Александр Саввин, Роман Шумихин
Со всеми предложениями, идеями и комментариями обращайтесь
к Сергею Скороходову (suralis-s@mtu-net.ru)
Сайт рассылки: http://gazette.linux.ru.net
Эту статью можно посмотреть по адресу: http://gazette.linux.ru.net/lg74/articles/rus-zhaoway.html
| http://subscribe.ru/
E-mail: ask@subscribe.ru |
Отписаться
Убрать рекламу |
| В избранное | ||
