К содержанию

8. Общие вопросы


Где я могу получить упомянутые файлы заголовков?

Если у вас их нет на вашей системе, вероятно, вам они не понадобятся. Ознакомьтесь с руководством для конкретной платформы. Если вы программируете под Windows, вам нужно всего лишь #include .

Что мне делать, если bind() сообщает «Адрес уже используется» или «Address allready in use»?

Вы должны использовать setsockopt() с опцией сокета SO_REUSEADDR. Дляя примера см. раздел bind() и раздел selct().

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

Используйте команду netstat. Для полной информации см. соответствующую ман-страницу, но вы получите некий результ и просто напечатав в терминале:

$ netstat

 

Сложность заключается в определении того, какой сокет какой программе принадлежит =)

Как я могу посмотреть таблицу маршрутизации?

Выполнив команду route или netstat -r

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

К счастью для вас, практически все машины реализуют виртуальное сетевое «устройство» — loopback, которое находится в ядре и эмулирует сетевую карту. (Это интерфейс, указанный как «Lo» в таблице маршрутизации.)

Представьте, что вы зашли на компьютер под именем «alpha«. Запустите клиент в одном окне и сервер в другом. Или запустите сервер в фоновом режиме (в unix — «server &») и запустите клиент в том же окне. Результатом работы loopback является то, что вы можете по сети соединиться с сервером alpha или сервером localhost и вы получите коммуникацию клиент-сервер, фактически не имея реальной сети!

Короче говоря, для того, чтобы машина общалась сама с собой «по сети», ни в какой сетевой код никаких изменений вносить не нужно! Ура!

В каких случаях можно сказать, что удаленная сторона закрыла соединение?

Вы можете узнать об этом точно, получив 0 в результате вызова recv()

Как реализовать утилиту «Пинг»? Что такое ICMP? Где я могу узнать больше о низкоуровневых сокетах и SOCK_RAW?

На все вопросы о низкоуровневых сокетах даны ответы в книге Ричарда Стивенса «UNIX Network Programming».

Как изменить или сократить таймаут ожидания при вызове connect()?

Вместо того чтобы дать вам точно такой же ответ, какой У. Ричард Стивенс бы вам дать, я просто отсылаю вас к разделу Lib / connect_nonb.c в UNIX Network Programming source code.

Суть в том, что вы создаёте дескриптор сокета, делаете его в неблокирующим, вызываете connect(), и если все идёт хорошо, connect() возвращает -1 сразу и глобальная переменная errno устанавливается в EINPROGRESS. Затем вы вызываете select() с произвольным таймаутом, передавая ему дескриптор сокета. Если не произойдёт таймаут, значит вызов connect() завершен. После этого вы должны будете использовать getsockopt() с аргументом SO_ERROR, чтобы получить возвращенное connect() значение, которое должно быть равно нулю, если ошибок не было.

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

Обратите внимание, это дополнительный плюс, позволяющий вашей программе делать что-то ещё, пока она ожидает подключения. Вы могли бы, например, установить таймаут в какое-нибуть достаточно низкое значение, скажем, 500 мс, и обновлять индикатор на экране каждый раз, когда происходит таймаут, а затем вызвать select() ещё раз. Вызвав select() и получив таймаут скажем, 20 раз, вы узнаете, что соединение установить не получается.

Как я уже говорил, в исходных кодах Стивенса есть отличный пример.

Как всё это проделать в Windows?

Во-первых, удалить Windows и установить Linux или BSD.;-). Нет, на самом деле, просто см. раздел о программировании под Windows во введении.

Как я всё это программировать для Solaris / SunOS? Я постоянно получаю ошибки компоновщика, когда пытаюсь скомпилировать!

Ошибки компилятора случаются потому, что SUNos не подключают автоматически библиотеки сокетов. См. раздел о программировании под Solaris / SunOS во введении; там есть примеры.

Как я могу реализовать Таймаут на вызов 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; // timeout!
if (n == 1) return 1; // error

// данные должны быть, так что вызываем 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) {
// timeout
} else {
// в буфере есть какие-то данные
}
.
.
.

 

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

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

Один из простых способов реализовать шифрование заключается в использовании SSL (Secure Sockets Layer), но это выходит за рамки данного руководства. ( см. OpenSSL Project для дополнительной информации.)

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

  • 1. Сервер считывает данные из файла (неважно, откуда)
  • 2. сервер шифрует / сжимает данные (вы реализуете эту часть)
  • 3. Сервер send() зашифрованные данные

Теперь наоборот:

  • 1. Клиент recv() зашифрованные данные.
  • 2. Клиент расшифровывает / распаковывает данные (вы реализуете эту часть)
  • 3. Клиент записывает данные в файл (или ещё что-то с ними делает)

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

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

Все, что Вам нужно сделать, чтобы использовать при этом мой код — найти место, где данные находятся между считыванием (обьявлением) и передачей (с использованием send()) по сети, и поместить туда некий код, осуществляющий шифрование.

Что за «PF_INET» я все время вижу? Это связано с AF_INET?

Да, связано. См. раздел о сокетах для деталей.

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

Для простоты предположим, что клиент выполняет connect(), send() и close() для соединения. (То есть, Есть никаких последующих системных вызовов без нового подключения клиента).

Процесс со стороны клиента заключается в следующем:

  • 1. connect() к серверу
  • 2. send(«/sbin/ls > /tmp/client.out»)
  • 3. close()

Сервер должен обработать данные и выполнить их:

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

Будь осторожен! Держать сервер, выполняющий всё, что говорит клиент — то же самое, что предоставить удаленный доступ к шеллу, и клиенты смогут сделать в системе что захотят. Например, в приведенном выше примере, что, если клиент посылает «rm -rf ~»? Это удалит все, что есть в вашей учетной записи, вот что!

Таким образом, было бы мудрым поступком предотвращать использование клиентом любых команд за исключением нескольких, о которых вы точно знаете, что они безопасны; например, как утилита foobar:

если (!strncmp (ул, «Foobar», 6)) {
Sprintf (sysstr, «% S> / TMP / server.out», ул);
системы (sysstr);
}

Но программа, к сожалению, по-прежнему небезопасна: Что делать, если клиент вводит «boobar; rm -rf ~»? Самое безопасное, что можно сделать — это написать процедуру, которая ставит слеш («») перед всеми не буквенно-цифровыми символами (включая пробелы, в случае необходимости) в тело команды.

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

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

Ты споткнулся об MTU-максимальный размер кадра, с которым может справится физическая среда. На локальной машине ты используешь loopback-устройство которое без проблем переварит пакет размером 8K и даже больше. Но по Ethernet, который может обрабатывать только 1500 байт с заголовком, ты попал в этот предел. Через модем, с MTU 576 (опять же, с заголовком), ты попал даже нижне предела.

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

Читайте раздел Сын инкапсуляции данных дял подробной информации о получении полного пакета данных с использованием нескольких вызовов recv().

Я под Windows и у меня нет системного вызова fork(). Что делать?

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

Поищите «fork()» или «POSIX» в разделах справки, которая идёт с VC++.

Если это не работает вообще, fork() можно заменить его Win32 эквивалентн: CreateProcess(). Я не знаю, как использовать CreateProcess() — он принимает миллиард аргументов, но они должны быть включены в документацию, поставляемую с VC++.

Я за брандмауэром, как я могу людям за пределами брандмауэра даль знать мой IP-адрес, чтобы они могли подключиться к моей машине?

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

Но это не означает, что все потеряно. С одной стороны, вы можете по-прежнему использовать connect() через брандмауэр, если он делает что-то вроде маскарадинга или NAT. Просто стройте дизайн вашей программы так, чтобы вы всегда инициировали соединение, и всё будет хорошо.

Если это не является удовлетворительным, вы можете задать вопрос своим системным администраторам, могут ли они «проткнуть дыру» в брандмауэре, чтобы люди могли связаться с вами. Брандмауэр может направить Вам соединение либо через его NAT, либо через прокси-сервер или что-то подобное.

Помните, что дырка в брандмауэре ничего хорошего не несёт. Вы должны убедиться, что не даете доступ к внутренней сети кому попало, ведь если вы новичок — сделать программное обеспечение безопасным намного сложнее, чем вы можете себе представить.

Не заставляйте вашего администратора сердиться на меня. 😉

Как мне написать анализатор пакетов? Как разместить мои Ethernet интерфейс в смешанный режим (promiscnous mode)?

Для тех, кто не в курсе, когда сетевая карта в «режиме прослушивания», она направляет в ОС все пакеты, а не только те, которые были обращены к данной машине. (Мы сейчас говорим об уровне Ethernet-адресов, а не IP — но так как Ethernet является низшим слоем, чем IP, IP-адреса и протоколы также эффективно перенаправляются).

Это является основой того, как работает сниффер пакетов. Сетевой интерфейс ставится в смешанный режим, то ОС получает каждый пакет, который идет по проводам. Вам нужен будет сокет того же типа и протокола, что и данные, которые вы хотите прочитать.

К сожалению, ответ на вопрос зависит от платформы, но если вы спросите Google, например, «Windows promiscuous ioctl» — что-то он вам да покажет.

Как я могу установить пользовательские таймауты для TCP или UDP сокета?

Это зависит от вашей системы. Вы можете поискать в интернете SO_RCVTIMEO и SO_SNDTIMEO (для использования с setsockopt()), чтобы узнать, поддерживает ли ваша система такую функциональность.

man-страница Linux предлагает использовать alarm() или setitimer() в качестве замены.

Как я могу узнать, какие порты доступны для использования? Есть ли список «официальных» номера портов?

Обычно это не проблема. Если вы пишете, скажем, веб-сервер, то хорошкй идеей будет использовать хорошо известный и стандартный порт 80 для вашего программного обеспечения. Если вы пишете только собственный специализированный сервер, выберите порт случайным образом (но больше, чем 1023) и попробуйте.

Если порт уже используется, вы получите сообщение об ошибке «Адрес уже используется» при попытке bind() Выберите другой порт. (Хорошей идеей может быть — позволить пользователю программного обеспечения указать альтернативный порт при помощи конфиг-файла, или параметра командной строки.)

Существует список официальных номеров портов, он поддерживается Internet Assigned Numbers Authority (IANA). Одно то, что что-то (выше 1023) присутствует в этом списке, не означает, что вы не можете использовать порт. Так, например, DOOM Id Software использует тот же порт, что «mdqs», что бы это ни значило. Все это имеет тот смысл, что никто другой на той же машине не сможет использовать этот порт, если его уже используете.

К содержанию