Главная страница

Guide to Network Сетевое программирование от Биджа


Скачать 1.34 Mb.
НазваниеGuide to Network Сетевое программирование от Биджа
Дата02.05.2019
Размер1.34 Mb.
Формат файлаpdf
Имя файлаbgnet_A4_rus.pdf
ТипGuide
#75918
страница9 из 13
1   ...   5   6   7   8   9   10   11   12   13

PF_INET
”, что это Это связано с
AF_INET
? Да, это так. Смотрите раздел по socket(). Как написать серверу, чтобы принял от клиента команды и выполнил их Давайте для простоты примем, что клиент подключается (connect()), посылает данные (send()) и закрывает соединение (close()) (те. для последующих вызовов клиент подключается вновь. Процесс для клиента такой
!
1. подключиться к серверу (connect())
2. послать (send(“/sbin/ls > /tmp/client.out”))
3. закрыть соединение (close()) Тогда как сервер принимает данные и выполняет их
!
1. принимает соединение от клиента (accept())
2. получает командную строку (recv(str))
3. закрывает соединение (close())
4. запускает команду (system(str)) Осторожно Позволить серверу выполнять команды клиента - это позволить ему вытворять с вашим аккаунтом всё что угодно когда он подключится к серверу. Например,
62
http://www.openssl.org/
40

Beej's Guide to Network что если клиент пришлёт команду “rm -rf

”? Это удалит всё в вашем аккаунте, вот что это Так что будьте мудрее и не позволяйте клиенту ничего исполнять кроме парочки утилит, которые, вызнаете, безопасны, как утилита foobar: if (!strncmp(str, "foobar", 6)) { sprintf(sysstr, "%s > /tmp/server.out", str); system(sysstr);
} К сожалению, это до сих пор небезопасно что если клиент пришлёт “foobar; rm -rf
”? Самая безопасное это написать маленькую программку, которая будет вставлять эскейп-символ (“
\
”) перед всеми не алфавитно-цифровыми символами (включая если можно пробелы) в аргументах команды. Как видите, безопасность это весьма большая проблема, если вы позволяете серверу выполнять команды клиента. Я посылаю массу данных, но зараз приходит только 536 или 1460 байт. Но если запуститься на моей локальной машине, то данные принимаются за один раз. В чём дело Вы превысили MTU - максимальный размер пакета, который может обработать физическая среда. На локальной машине вы используете loopback устройство, которое без проблем может обрабатывать 8K или больше. А на Ethernet, который может обрабатывать только 1500 байт с заголовком, вы этот предел превысили. С модемом с
576 MTU (опять же, с заголовком, вы превысили этот нижний предел. Прочтите раздел Дитя Инкапсуляции Данных о том как принимать целые пакеты данных многократным вызовом recv(). На платформе Windows у меня нет системного вызова и любого вида struct

sigaction. Что делать Если они где и есть, так это в библиотеках POSIX, которые могут поставляться с вашим компилятором. Поскольку платформы Windows у меня нет, я не могу ответить, но, помнится, уесть уровень совместимости с POSIX, где fork() должен быть. (И может быть даже sigaction.) Поищите в системе помощи VC++ “fork” или “POSIX” и, может быть, он даст вам какую-нибудь нить. Если это совсем не работает, выбросьте fork()/sigaction на помойку и замените их эквивалентом из Win32: CreateProcess(). Яне знаю как CreateProcess() работает - она принимает базиллион аргументов, но это должно быть в документации, поставляемой с VC++. Я под брандмауэром - как указать людям за ним мой IP адрес, чтобы они подключились к моей машине К сожалению, цель брандмауэра - предотвратить подключение людей за брандмауэром к машинам под ним, так что разрешение такого подключения повсеместно рассматривается как брешь в безопасности. Но нельзя сказать, что всё потеряно. Одна вещь, вы до сих пор можете подключаться через брандмауэр, даже если он использует какой-нибудь маскарадинг или NAT или что- то вроде этого. Просто напишите программу так, чтобы инициатором соединения всегда были вы, и будет вам счастье.
63

Beej's Guide to Network Если это не удовлетворяет, попросите вашего системного администратора проткнуть дыру в брандмауэре, чтобы люди могли к вам подключаться. Брандмауэр может пробираться к вам либо через свои NAT программы либо через прокси либо через нечто подобное. Но знайте, что дыра в брандмауэре так просто не обнаруживается. Вы должны быть уверены, что не даёте плохим людям доступа во внутреннюю сеть, если вы новичок, запомните, сделать программы безопасными намного труднее, чем это можно представить. Не сводите сума вашего системного администратора вместе со мной. ;-) Как написать анализатор пакетов Как переключить мой Ethernet интерфейс в беспорядочный режим Для тех кто не знает, когда сетевая карта находится в беспорядочном режиме, она пропускает в операционную систему ВСЕ пакеты, а не только адресованные этой конкретной машине. (Здесь мы говорим не об IP адресах, а об адресах уровня, и, поскольку уровень Ethernet ниже, чем IP, все IP адреса замечательно передаются. Смотрите раздел Низкоуровневый Вздор и Теория сетей) Это основа того, как работает анализатор пакетов. Он переключает интерфейс в беспорядочный режим и ОС получает все ходящие по проводам пакеты. Вам нужно иметь сокет некоторого типа, через который вы можете читать эти данные. К сожалению, ответ на этот вопрос меняется в зависимости от платформы, но если выдадите запрос, например, “windows promiscuous ioctl”, то может что-то и получите. Кроме того, достойно выглядит статья в Linux Journal . Как установить своё значение таймаута для TCP или UDP сокета?
Это зависит от вашей системы. Можете поискать в сети и для использования с setsockopt()), чтобы узнать поддерживает ли ваша система такие операции.
Man страницы Linux предлагают вместо этого использовать или
setitimer(). Как определить доступные порты Есть ли список официальных номеров портов Обычно это не проблема. Если выпишете, скажем, web сервер, то неплохо бы использовать широко известный порт 80. Если же выпишете свой специализированный сервер, выберите случайный порт (но больше 1023) и попробуйте. Если порт уже занят, то bind() выдаст ошибку “Address already in use”. Выберите другой порт. (Неплохая идея позволить пользователю самому определить альтернативный порт в config файле либо ключом в командной строке) Есть список официальных номеров портов , определяемый Internet Assigned Numbers
42
Authority (IANA). То что какой-либо порт (старше 1023) находится в этом списке не означает, что вы не можете его использовать. Например, DOOM от Id Software использует такой порт как “mdqs”, каким бы он ни был. Важно лишь чтобы в это время никто кроме вас не использовал этот порт на этой же машине.
64
http://interactive.linuxjournal.com/article/4659 41
http://www.iana.org/assignments/port-­‐numbers
42

Beej's Guide to Network Programming
9. Man Страницы В мире Unix много мануалов. В них есть маленькие разделы, которые описывают имеющиеся в вашем распоряжении функции. Конечно, мануал обычно слишком велик чтобы набирать его. Я имею ввиду, никто из мира Unix, включая меня, не любит так много печатать. В действительности я могу до бесконечности печатать о том, как сильно я предпочитаю быть кратким, но вместо этого я буду краткими не стану докучать вам пространными пламенными речами о том каким чрезвычайно удивительно кратким я предпочитаю быть практически во всех случаях во всей их полноте. Аплодисменты Спасибо. Я добрался до того, что в мире Unix эти страницы называются “man страницами и я включил сюда мои собственные личные усечённые варианты для вашего читательского удовольствия. Вся штука в том, что многие из этих функций суть более общего назначения, чем я описываю, ноя намереваюсь представить только ту часть, которая соответствует Программированию Интернет Сокетов. Погодите Это ещё не все недостатки моих man страниц Они неполные и показывают только основы из руководства. Они даже более man страницы, чем в реальном мире. Они отличаются от таких же в вашей системе. Заголовочные файлы могут различаться для определённых функций в вашей системе. Параметры функций могут быть другими для определённых функций в вашей системе. Если вам нужна настоящая информация, посмотрите man страницы вашей Unix, напечатав man whatever, где “whatever” означает что-то необычайно вам интересное, вроде “accept”. (Я уверен, весть нечто подобное в их секции помощи. Но “man” лучше, потому что на один байт короче, чем “help”. Unix побеждает снова) Но если они такие ущербные, зачем вообще включать их в это Руководство Есть несколько причинно лучшие из них то, что (a) эти версии специально заточены под сетевое программирование, и (b) эти версии содержат примеры И говоря о примерах, я не намерен пускаться вовсе проверки ошибок потому что это по-настоящему увеличивает длину кода. Новы должны выполнять проверку ошибок преогромное количество раз при каждом системном вызове пока не будете на 100% уверены, что программа не собирается сбоить и, возможно, даже после этого
65

Beej's Guide to Network Programming
9.1. Принимает входные подключения на слушающем сокете. Прототип
#include
!
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
Описание*
Как только вы пошли сквозь хлопоты получения SOCK_STREAM сокета и поставили его для получения входящих соединений через listen(), вы вызываете accept() чтобы получить новый дескриптор сокета для последующего общения столько что подключённым клиентом. Старый сокет, использовавшийся для прослушивания, ещё здесь и будет использован для дальнейшего приёма ом новых вызовов по мере их поступления.
s Дескриптор слушаемого сокета.
addr Заполняется адресом подключающегося сайта.
addrlen Заполняется размером (sizeof()) структуры, возвращённой в параметре
addr. Можно спокойно игнорировать, если полагаете, что получили назад struct sockaddr_in, потому что именно такой тип вы передавали в параметре addr.
accept() обычно блокируется ивы можете использовать select(), чтобы заранее взглянуть готов ли он к чтению. Если готов, значит новое подключение ждёт accept()- а Йес! По другому, с помощью fcntl() вы можете установить на слушающем сокете флаг O_NONBLOCK, выбирая возврати установку в EWOULDBLOCK. Возвращаемый ом дескриптор определяет уже добросовестно открытый и подключённый к удалённому хосту сокет. Вам нужно вызвать close() по завершении работы с ним. Возвращаемое значение возвращает дескриптор только что подключённого сокета, или
-1 при ошибке, при этом соответствующим образом установив errno. Пример sockaddr_storage their_addr; socklen_t addr_size; struct addrinfo hints, *res; int sockfd, new_fd;
!
// сначала заполняем адресные структуры с помощью getaddrinfo():
!
memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC;
// использовать либо IPv4 либо IPv6 hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE;
// заполнить мой IP для меня getaddrinfo(NULL, MYPORT, &hints, &res);
!
// создать сокет, связать и слушать
!
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); bind(sockfd, res->ai_addr, res->ai_addrlen);
66

Beej's Guide to Network Programming
listen(sockfd, BACKLOG);
!
// теперь принять входящие подключения
!
addr_size = sizeof their_addr; new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);
!
// можно беседовать по дескриптору сокета new_fd! Смотри также, getaddrinfo(), listen(), struct sockaddr_in!
67

Beej's Guide to Network Programming
9.2. Связывает сокет с IP адресом и номером порта. Прототип
#include
!
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
Описание*
Когда удалённая машина хочет связаться с вашей серверной программой, ей для этого нужны IP адрес и номер порта. А позволяет ей это сделать вызов bind(). Сначала вы вызываете getaddrinfo() и заполняете struct sockaddr адресом назначения и информацией порта. Затем вы вызываете socket() чтобы получить дескриптор сокета и передаёте сокет и адрес в bind(), и вот IP адрес и порт волшебным образом (используя настоящее волшебство) привязан к сокету! Если вы не знаете своего IP адреса, или вам известно, что у вашей машины только один IP адрес, или вам безразлично, какие IP адреса использованы на вашей машине, просто установите флаг AI_PASSIVE
в параметре при вызове getaddrinfo(). При этом в часть IP адреса в struct sockaddr записывается специальное значение, которое указывает bind(), что ей нужно автоматически заполнить этот IP адрес хоста. Что, что Что за специальное значение записывается в IP адрес struct sockaddr чтобы автоматически установить адрес текущего хоста? Я скажу, но помните, что это происходит только при заполнении struct sockaddr вручную, иначе воспользуйтесь результатом getaddrinfo(), как указано выше. В IPv4, поле структуры struct sockaddr_in устанавливается INADDR_ANY. В IPv6, в поле
sin6_addr структуры sockaddr_in6 записывается значение глобальной переменной
in6addr_any. Или, если вы объявляете новую struct in6_addr, вы можете инициализировать её IN6ADDR_ANY_INIT. Напоследок, параметр addrlen должен быть установлен в sizeof my_addr. Возвращаемое значение
!
Возвращает 0 при успехе или -1 в случае ошибки (errno устанавливается соответственно. Пример современный способ работы с getaddrinfo()
!
struct addrinfo hints, *res; int sockfd;
!
// сначала заполняем адресные структуры с помощью getaddrinfo():
!
memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC;
// использовать либо IPv4 либо IPv6 hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE;
// заполнить мой IP для меня
!
getaddrinfo(NULL, "3490", &hints, &res);
!
// создать сокет:
!
// (вам нужно прогуляться по связанному списку "res" и проверить на ошибки)
68

Beej's Guide to Network Programming
!
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
!
// связать спортом, переданным getaddrinfo():
!
bind(sockfd, res->ai_addr, res->ai_addrlen);
!
// пример упаковки структуры вручную, IPv4
!
struct sockaddr_in myaddr; int s;
!
myaddr.sin_family = AF_INET; myaddr.sin_port = htons(3490);
!
// можете указать IP адрес
!
inet_pton(AF_INET, "63.161.169.137", &(myaddr.sin_addr));
!
// или позволить выбрать его автоматически myaddr.sin_addr.s_addr = INADDR_ANY;
!
s = socket(PF_INET, SOCK_STREAM, 0); bind(s, (struct sockaddr*)&myaddr, sizeof myaddr); Смотри также, socket(),
struct sockaddr_in, struct in_addr!
!
69

Beej's Guide to Network Programming
9.3. Подключает сокет к серверу. Прототип
#include
!
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
Описание*
После того, как вы создали дескриптор сокета вызовом socket(), вы можете подключить его к удалённому серверу системным вызовом connect(). Вам нужно только передать ему дескриптор сокета и адрес сервера, с которым вам захотелось познакомиться поближе. (Да, и длину адреса, которую принято передавать таким функциям) Обычно эту информацию получают как результат вызова getaddrinfo(), но, если хотите, можете заполнить свою собственную struct sockaddr сами. Если вы ещё не вызывали bind() с этим дескриптором сокета, он автоматически привязывается к вашему IP адресу и случайному локальному порту. Обычно это просто замечательно для вас, если вы не сервер, поскольку вам безразличен номер вашего локального порта. Если номер удалённого порта важен, то укажите его в параметре в
serv_addr. Вы можете вызвать bind() если действительно хотите, чтобы сокет вашего клиента был привязан к определённому
IP адресу и порту, но это бывает весьма редко. Как только сокет подключён (connect()), вы можете спокойно посылать (send()) и принимать (recv()) данные от него согласно велению вашего сердца. Отдельное примечание если вы подключили connect() SOCK_DGRAM
UDP сокет к уделённому хосту, то можете использовать send() и recv() также как sendto() и
recvfrom(). Если хотите. Возвращаемое значение
!
Возвращает 0 при успехе или -1 в случае ошибки (errno устанавливается соответственно. Пример соединиться спорт сначала заполняем адресные структуры с помощью getaddrinfo(): memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC;
// использовать либо IPv4 либо IPv6 hints.ai_socktype = SOCK_STREAM;
!
// в это строке можно указать "80" вместо "http": getaddrinfo("www.example.com", "http", &hints, &res);
!
// создать сокет: sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
!
// соединить с адресом и портом, переданным getaddrinfo(): connect(sockfd, res->ai_addr, res->ai_addrlen);
!
70

Beej's Guide to Network Смотри также, bind()
71

Beej's Guide to Network Programming
9.4. Закрывает дескриптор сокета. Прототип

!
int close (int s);
Описание*
После того как вы закончили использовать сокет в любой состряпанной вами сумасбродной затее и больше не хотите посылать (send()) или принимать (recv()) данные, и вообще ничего с ним не делать, вы можете закрыть (close()) его ион будет освобождён, дабы более никогда не использоваться.
Удалённая сторона может узнать об этом одним из двух способов. Первый Если удалённая сторона вызывает recv(), он возвращает 0. Второй удалённая сторона вызывает send(), он примет сигнал SIGPIPE и вернёт -1, errno будет установлен в
EPIPE. Пользователям Windows: функция, которую вам нужно использовать называется
closesocket(), а не close(). Если вы попробуете использовать close(), то, возможно, Windows рассердится… И вам не понравится когда она сердится. Возвращаемое значение!
Возвращает 0 при успехе или -1 в случае ошибки (
1   ...   5   6   7   8   9   10   11   12   13


написать администратору сайта