Это архив сайта coldflame.by.ru, он не обновлялся с 2007 года. Мой современный сайт тут: http://leonid.shevtsov.me.
Домой! Обо мне Специально для РИ-06-1 Разнообразное... барахло, короче :) Программы и прочее Статьи и переводы Блог SmartDaemon
Предыдущая ОглавлениеСледующая

7. FAQ

Где мне достать файлы заголовков?

Если на твоей системе их нет, то скорее всего они тебе не нужны. Почитай в справке. Под Windows единственный заголовок, который нужно подключить, это #include <winsock.h>.

Что делать когда bind() возврващает "Address already in use" (Адрес уже используется)?

Вызови setsockopt() с параметром SO_REUSEADDR для слушающего сокета. Пример есть в разделе о bind() и разделе о select().

Как получить список открытых сокетов в системе?

Используй netstat. Подробнее написано в man, но ты получишь хорошие результаты, написав просто:

$ netstat

Единственная проблема - с определением, какие программы с ними связаны. :-)

Как увидеть таблицу маршрутов?

Используй команду route (в каталоге /sbin на системах Linux) или команду netstat -r.

Как запустить клиент и сервер, если у меня всего одна машина? Разве для написания сетевых программ не нужна сеть?

К счастью, практически все машины поддерживают "виртуальное сетевое устройство", которое сидит в ядре ОС и притворяется сетевой картой. (В таблице маршрутов этот интерфейс называется "lo".)

Допустим, твоя машина называется "goat". Запусти клиент в одном окне и сервер в другом. Или запусти сервер в фоне ("server &") и клиент в том же окне. Можешь вызывать client goat или client localhost (так как "localhost" наверняка определен в твоем /etc/hosts) и клиент будет работать с сервером без сети!

А в коде вообще не нужно ничего менять! Обалдеть!

Как узнать, что удаленный компьютер закрыл соединение?

recv() в таком случае вернет 0.

Как создать утилиту "ping"? Что такое ICMP? Где узнать про "raw sockets" (SOCK_RAW)?

На все вопросы по "сырым сокетам" есть ответ в книгах В. Ричарда Стивенса "Сетевое программирование под UNIX". Посмотри в списке книг.

Как компилировать под Windows?

Удали Windows и поставь Linux или BSD. };-). На самом деле лучше почитай замечания о Windows.

Как компилировать под Solaris/SunOS? Компоновщик выкидывает ошибку!

Ошибки компоновщика связаны с тем, что системы Sun не прилинковывают автоматически библиотеки сокетов. Почитай замечания о Solaris/SunOS.

Почему select() возвращается при сигнале?

Сигналы заставляют блокирующие системные вызовы возвращать -1, сохраняя EINTR в errno. При установке обработчика сигнала с помощью sigaction(), можно указать флаг SA_RESTART, который должен перезапустить системный вызов после прерывания.

Тем не менее, это почти никогда не работает.

Мое любимое решение - с применением оператора goto. Это бесит профессоров, так что вперед!

select_restart:
if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
    if (errno == EINTR) {
        // нас прервал сигнал, перезапускаем
        goto select_restart;
    }
    // обрабатываем настоящую ошибку:
    perror("select");
} 

Конечно совсем не обязательно использовать goto в этом случае, можно использовать и другие структуры. Но по-моему goto выглядит гораздо чище и элегантнее.

Как заставить recv() возвращаться по истечению времени?

Используй select()! Он позволяет указать время ожидания тех дескрипторов, из которых ты собираешься читать. Можно и упаковать все в одну функцию, вроде:

#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

int recvtimeout(int s, char *buf, int len, int timeout)
{
    fd_set fds;
    int n;
    struct timeval tv;

    // указываем набор сокетов
    FD_ZERO(&fds);
    FD_SET(s, &fds);

    // указываем таймаут
    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    // ждем данных или таймаута
    n = select(s+1, &fds, NULL, NULL, &tv);
    if (n == 0) return -2; // таймаут!
    if (n == -1) return -1; // ошибка

    // получены данные, вызываем recv()
    return recv(s, buf, len, 0);
}
.
.
.
// Пример использования recvtimeout():
n = recvtimeout(s, buf, sizeof(buf), 10); // ждем 10 секунд

if (n == -1) {
    // ошибка
    perror("recvtimeout");
}
else if (n == -2) {
    // таймаут
} else {
    // получили данные
}
.
.
. 

Обрати внимание, recvtimeout() возвращает -2 при таймауте. Почему не 0? Если ты помнишь, recv() возвращает 0 если удаленная сторона закрыла соединение. Так как -1 уже значит "ошибка", мне пришлось использовать -2.

Как шифровать или упаковыват данные до отправки?

Можно использовать SSL (secure sockets layer, уровень безопасных сокетов), но они выходят за пределы этого руководства.

Если же ты реализуешь свою собственную систему сжатия или шифрования, тебе нужно всего-навсего пропускать через нее данные на входе и выходе.

  1. сервер читает данные из файла (или еще откуда-то)
  2. сервер шифрует данные
  3. сервер отсылает зашифрованные данные

И наоборот:

  1. клиент получает зашифрованные данные
  2. клиент расшифровывает данные
  3. клиент пишет данные в файл (или еще куда-то)

Точно так же выполняется сжатие/распаковка. Можно делать и сжатие, и шифрование! Только сначала нужно сжимать, а потом уже шифровать. :)

Пока клиент выполняет шаги в противоположном порядке, проблем не будет, сколько бы шагов не было в процедуре отправки.

Так что просто найди место между чтением данных и их отправкой в сеть (с помощью send()), и вставь туда код шифрования.

Что такое "PF_INET"? Как он относится к AF_INET?

Относится. Почитай раздел о socket(), если интересно.

Как написать сервер, который принимает командную строку от клиента и выполняет ее?

Для простоты, пусть клиент соединяется, отсылает строку и отсоединяется (т.е. выполняет только одну посылку.)

Псевдокод клиента:

  1. connect(сервер)
  2. send("/sbin/ls > /tmp/client.out")
  3. close(сокет)

А сервер получает и выполняет данные:

  1. accept(соединение)
  2. recv(командную строку)
  3. close(соединение)
  4. system(командная строка), чтобы выполнить команду

Осторожно! Давать возможность клиенту выполнять любые команды - значит, предоставлять удаленный доступ к системе. Что произойдет, если клиент пошлет "rm -rf ~"? Он удалит все файлы в твоем профиле, вот что!

После чего ты будешь умнее и ограничишь доступ клиента несколькими безопасными утилитами, например утилитой foobar:

if (!strcmp(str, "foobar")) {
    sprintf(sysstr, "%s > /tmp/server.out", str);
    system(sysstr);
} 

Это все еще небезопасно, ведь клиент может послать "foobar; rm -rf ~". Лучше написать функцию, которая поставит "\" перед всеми не алфавитно-цифровыми символами (включая пробелы, если нужно) в аргументах команды.

Безопасность - очень важная штука, когда сервер выполняет то, что ему говорит клиент.

Я шлю пачку данных, а recv() получает только 536 или 1460 байт за раз. А на локальной машине данные приходят за один вызов. Что происходит?

Скорость ограничивается MTU - максимальным объемом данных, который физический носитель может передать за раз. На локальной машине виртуальная сеть может спокойно передать 8K и даже больше. Ethernet передает по 1500 байт, включая заголовок. Модемное соединение передает по 576 байт (опять-таки с заголовком), так что ограничение еще меньше.

Конечно, нужно наверняка передавать все данные. (Посмотри на функцию sendall().) И так же вызывать recv() в цикле, пока не получишь все данные.

Почитай раздел Применяем инкапсуляцию, где объясняется, как получать пакет за несколько вызовов recv().

Я использую Windows и не могу найти ни функцию fork(), ни struct sigaction. Что делать?

Если они где-то и есть, то в библиотеке POSIX, которая может поставляться с компилятором. Поскольку у меня нет Windows, я не могу ответить на этот вопрос, но, помнится, у Microsoft есть уровень совместимости с POSIX и там должна быть fork(). (И, может, даже sigaction.)

Поищи "fork" или "POSIX" в справке по VC++.

Если это никак не работает, забудь про fork() и sigaction и замени их виндовским эквивалентом: CreateProcess(). Я не знаю, как ее использовать - она берет кучу аргументов. Посмотри в справке по VC++.

Как передать данные по TCP/IP с шифрованием?

Посмотри проект OpenSSL.

Я защищен файрволом - как мне сообщить мой IP-адрес людям за файрволом, чтобы они могли со мной соединиться?

Увы, назначение файрвола - запретить людям соединяться с машинами за файрволом, так что такая возможность расценивается как брешь системы безопасности.

Но это еще не конец. Зачастую можно все равно соединиться через файрвол, если он использует NAT или еще что-то в этом роде. Разрабатывай программы так, чтобы ты сам начинал соединение, и все будет в порядке.

Если этого недостаточно, попроси админов выделить тебе дырку в файрволе, чтобы с тобой могли соединяться.

Учти, что дырка в файрволе - это не шутки. Убедись, что вредители не получат доступа к внутренней сети. Если ты только начинаешь, разработка безопасных программа намного сложнее, чем тебе кажется.

И не делай так, чтобы твой сисадмин ругал меня. ;-)


Предыдущая ОглавлениеСледующая