Здравствуйте!
Задача:
Создать приложение демон-процесс, слушающий и обрабатывающий запросы к TCP порту основанные на HTTP, выдающий в результате обработки запроса HTML-контент взятый из базы данных mysql, выполняющий перекодирование выдаваемого контента в желаемую/затребованную кодировку, которая устанавливается либо в самом запросе, либо в настройках его инициализационных параметров.
К сожалению, никогда не писал С++ программ для работы в сети, поэтому не представляю себе, что и в какой последовательности работает.
Я написал следующую программу:int main (int argc, char * const argv[]) {
struct sockaddr_in addr_info;
int add_len = sizeof(addr_info);
int ret_addr, sock_id;
char* buf = new char[256];
bzero(&addr_info, sizeof(addr_info));
addr_info.sin_family = AF_INET;
addr_info.sin_port = 8080;
addr_info.sin_addr.s_addr = INADDR_ANY;sock_id = socket(AF_INET, SOCK_STREAM, 0);
if (sock_id==-1) {cout<< "Error create socket\n"; return -1;}
if (bind(sock_id, (struct sockaddr*)&addr_info, sizeof(addr_info))==-1) {cout<< "Error bind\n"; return -1;}
listen(sock_id, 5);
ret_addr = recv(sock_id, buf, 256, 0);
// ret_addr = accept(sock_id, (struct sockaddr*)&addr_info, (socklen_t*)&add_len);
return 0;
}Но я так понимаю, что это совсем не в ту сторону. Единственное, что тут правильно - это socket().
Пожалуйста, подскажите схему приложения-реализации данной задачи, что за чем следует, а я постараюсь по Вашей схеме написать реализацию.
Заранее благодарен.
Алексей
Это какой то выкидыш - огрызок тут даже половины нет
Вот курите тут по ссылкам ниже .............http://www.gnu.org/s/hello/manual/libc/Server-Example.html#S...
http://www.w3.org/Protocols/rfc2616/rfc2616.html
>[оверквотинг удален]
> ret_addr = recv(sock_id, buf, 256, 0);
> // ret_addr = accept(sock_id, (struct sockaddr*)&addr_info, (socklen_t*)&add_len);
> return 0;
> }
> Но я так понимаю, что это совсем не в ту сторону. Единственное,
> что тут правильно - это socket().
> Пожалуйста, подскажите схему приложения-реализации данной задачи, что за чем следует,
> а я постараюсь по Вашей схеме написать реализацию.
> Заранее благодарен.
> Алексей
Спасибо.
Только мне кажется, что это не совсем то: тут реализована клиент-серверная модель (как и во всех других примерах), а мне надо, судя по условию задачи, что-то вроде tcpdump. Хотя я могу и ошибаться.
Такой вопрос: если я указываю программе 80 порт и у меня запущен веб-сервер, они не будут конфликтовать?
Если на разных машинках то не будут> Спасибо.
> Только мне кажется, что это не совсем то: тут реализована клиент-серверная модель
> (как и во всех других примерах), а мне надо, судя по
> условию задачи, что-то вроде tcpdump. Хотя я могу и ошибаться.
> Такой вопрос: если я указываю программе 80 порт и у меня запущен
> веб-сервер, они не будут конфликтовать?
У мнея все на одной.
Значит, на время отладки веб-сервер выключить?
Ну выходит да
> У мнея все на одной.
> Значит, на время отладки веб-сервер выключить?
> Только мне кажется, что это не совсем то: тут реализована клиент-серверная модель
> (как и во всех других примерах), а мне надо, судя по
> условию задачи, что-то вроде tcpdump.Нет, ничего подобного. Вам как раз нужен самый обычный TCP сервер. Вообще, не очень понятно, зачем вам это писать. Всё уже давно написано: возьмите нормальный веб-сервер и пользуйтесь на здоровье.
> Такой вопрос: если я указываю программе 80 порт и у меня запущен
> веб-сервер, они не будут конфликтовать?Прежде чем писать что-то на тему TCP/IP или даже просто использовать его, неплохо было бы почитать, что это вообще такое. Если вы даже не понимаете, что такое TCP порт, начните с чего-нибудь общего. Хоть бы и с википедии:
http://ru.wikipedia.org/wiki/%D0%9F%D0%B...)P.S. Нет совершенно ничего зазорного в том, что вы чего-то не знаете, ведь это можно сказать абсолютно про любого человека. Но не пытаться узнать что-то в наш век сверхдоступности информации - это по-моему тревожная тенденция. Желаю вам её исправить поскорее.
Конечно оно так.
Насчет - зачем оно надо: это тестовое задание на знание взаимодействия С++ и сетей.
Насчет - знаю-незнаю - спорный вопрос: я, например, специализируюсь на администрировании сетей, но понятия не имею, как программа инициирует взаимодействие в сети и куда чего пишет.
Насчет документации - хороший вопрос: когда ее начинать читать? Я уверен, что не на этапе изучения предмета а в момент понимания логики работы пытаться постичь нюансы. Я не встречал документации, упрощающей понимание предмета изучения. Вы нигде в документации по ОС не найдете простого определение ядра ОС: на мой взгляд OC - просто обработчик прерывания от таймера (в самом общем случае). Если человек поймет суть работы OC^ ему будет проще читать документацию про страшные менеджеры памяти и планировщики задач: он в уме уже будет представлять себе очереди потоков и понимание кода многопоточных приложений будет проще, если все представить на элементарном уровне.
По поводу TCP:
За два дня я получил много советов и ссылок, и каждый рассмотренный мною пример реализован по-разному. Все, что общего во всех примерах - это socket(). Далее набор функций варьируется от примера к примеру: bind, listen, connect,... Много хорошей документации по всем этим функциям, но нигде не написано, что происходит с пакетом, когда он попадает в лапы нашей неумелой программы. Так что пока моя программа похожа на слона в посудной лавке.
Меня утешает тот лишь факт, что Вы считаете эти вещи достаточно тривиальными - значит, в них просто разобраться. Я только пока не нашел, в чем соль...
> я, например, специализируюсь на администрировании сетей, но понятия не имею,
> как программа инициирует взаимодействие в сети и куда чего пишет.То есть, тот факт, что два разные программы, процессы, нити,..., не могут слушать
один и тот же порт, вам не известен? Где Вы говорите админ, ну так на будущие,
чтоб на работу не брать оттуда?!
> То есть, тот факт, что два разные программы, процессы, нити,..., не могут
> слушать
> один и тот же порт, вам не известен? Где Вы говоритеТо что этого не умеет ванильный линукс, вовсе не значит, что этого не умеют другие системы.
>> То есть, тот факт, что два разные программы, процессы, нити,..., не могут
>> слушать
>> один и тот же порт, вам не известен? Где Вы говорите
> То что этого не умеет ванильный линукс, вовсе не значит, что этого
> не умеют другие системы.А одновременно? Пёрнул, так раздувай...
А то ведь народ подумает, что Вы там на Inferno/Plan9,
иль мультеплесирование портов на полуаппаратных цисках.
иль мы в гугле нарыли флаги SO_REUSEADDR | SO_REUSEPORT ?
> иль мы в гугле нарыли флаги SO_REUSEADDR | SO_REUSEPORT ?Именно.
Ваш лузл пошел в коллекцию к:
>Так fork() возвращает PID потомка, который ты присваиваешь переменной pid.
>Далее он у тебя сравнивается с НОЛЁМ, НОЛЁВОЙ PID только у ЯДРА!!!!(c)pavlinux
Насчет одновременного владения портом:
Конечно же, любой администратор (и даже полу-администратор, или же вообще типа-администратор) знает, что два так называемых "процесса" не могут одновременно слушать один и тот же порт. Да нам даже и знать не надо - система нам сама скажет, что порт уже слушается другим сервисом, и мы с Вами не раз это сообщение получали, из чего можем сделать логические выводы, что порт все-таки штука неделимая для процессов.
Теперь давайте посмотрим на ситуацию с другой стороны:
Допустим, веб-сервер слушает 80 порт. В то же время я могу "слушать" 80 порт tcpdump, а про netfilter я уже вообще молчу. Выходит, в самом общем случае нет процесса-монополиста порта. Все зависит от уровня в модели OSI ?
Вот моя программа:
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;
int main (int argc, char * const argv[]) {
struct sockaddr_in addr_info;
int add_len = sizeof(addr_info);
int ret_addr, sock, n;
char* buf = new char[256];
// socket
bzero(&addr_info, sizeof(addr_info));
addr_info.sin_family = AF_INET;
addr_info.sin_port = 80;
addr_info.sin_addr.s_addr = inet_addr("127.0.0.1");//INADDR_ANY;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock==-1) {cout<< "Error create socket\n"; return -1;}
// bind
if (bind(sock, (struct sockaddr*)&addr_info, sizeof(addr_info))==-1) {cout<< "Error bind\n"; close(sock); return -1;}
// listen
if (!listen(sock, 5)) {
ret_addr = accept(sock, (struct sockaddr*)&addr_info, (socklen_t*)&add_len);
n = read(ret_addr, buf, 255);
// ret_addr = recv(sock, buf, 256, 0);
cout << buf << "\n";
}
else {
cout << "Error listen ";
}
close(sock);
return 0;
}
Даже не могу сказать, что в ней не работает.
Если запускать в отладчике - то виснет на accept.
Если просто запустить - выполняется, ничего не происходит, не заканчивается.
Внутренний веб-сервер остановлен, обращаюсь к внутреннему хосту, а также к любому внешнему из браузера во время работы программы - ничего.
Когда мы указываем порт и адрес в структуре - их обязательно переводить функциями, или можно указать, как в моей программе?
И еще: как указать :
локальный хост?
конкретный хост?
Любой хост?
Пока все.
> addr_info.sin_port = 80;addr_info.sin_port = htons(80);
> n = read(ret_addr, buf, 255);
> // ret_addr = recv(sock, buf, 256, 0);
> cout << buf << "\n";Вам кто-то \0 в buf[n] пообещал?
> Даже не могу сказать, что в ней не работает.
Все работает, если найдете машину с сетевым порядком байт, то даже 80й порт слушать будет.
> Если запускать в отладчике - то виснет на accept.
попробуйте конектиться на 20480
> Когда мы указываем порт и адрес в структуре - их обязательно переводить
> функциями, или можно указать, как в моей программе?Можно и как у вас (оно ж собирается и работает), но лучше функциями.
> И еще: как указать :
> локальный хост?INADDR_LOOPBACK
> конкретный хост?например как у вас
> Любой хост?Если любой это все локальные адреса машины то у вас там правильная константа закомментирована или 0.0.0.0
А если любой это вообще любой, то линукс вроде не умеет делать bind() на чужие адреса, а в *BSD setsockopt с SO_BINDANY
>> addr_info.sin_port = 80;
> addr_info.sin_port = htons(80);Если так, то bind выпадает, тогда уж htons(8080);
>> Если запускать в отладчике - то виснет на accept.
> попробуйте конектиться на 20480Вы имеете ввиду, что порты ниже 1024 закрыты для пользовательских приложений?
>> И еще: как указать :
>> локальный хост?
> INADDR_LOOPBACKТут тоже bind выпадает
Я понимаю, что для полной реализации модели нужна клиентская часть, но я предполагаю, что моя программа услышит запрос из браузера 127.0.0.1:80 (или 8080)
>>> addr_info.sin_port = 80;
>> addr_info.sin_port = htons(80);
> Если так, то bind выпадает, тогда уж htons(8080);Порт можете указать какой хочется... главное чтоб свободен был.
> Вы имеете ввиду, что порты ниже 1024 закрыты для пользовательских приложений?
Нет. Я хочу сказать что нужен сетевой порядок байт.
>> INADDR_LOOPBACK
> Тут тоже bind выпадаетЕсли он вам возвращает, что то отличное от EADDRINUSE или EACCES то приведите кусок кода и ошибку.
> предполагаю, что моя программа услышит запрос из браузера 127.0.0.1:80 (или 8080)
Услышит как только вы сделаете правильный bind() (я надеюсь вы браузер на этой же машине запускаете)
PS: прочитали бы вы какую-нибудь хорошую книжку.
Так, кое-что начинает проясняться.
указываем порт 8080, INADDR_ANY и из браузера стучимся localhost:8080 - получаем запрос от браузера.
Теперь как нам правильно указать 80 порт, чтобы bind не выпадал в -1, и мы могли стучаться просто localhost ?
Насчет свободных портов:
Если bind не хочет слушать 80 порт, значит его уже кто-то слушает?
Apache остановлен, кто еще может его держать и как посмотреть список слушающих процессов?
> Теперь как нам правильно указать 80 порт, чтобы bind не выпадал в
> -1, и мы могли стучаться просто localhost ?точно также как и 8080 или любой другой порт.
> Насчет свободных портов:
> Если bind не хочет слушать 80 порт, значит его уже кто-то слушает?Или нет прав.
> Apache остановлен, кто еще может его держать и как посмотреть список слушающих
> процессов?man netstat
Иди почитайте книжку, например Стивенса
Да, Вы правы - просто нет прав. Если запускать из-под sudo, то 80 порт слушает.
Насчет книжек и Стивенса, в частности, - читал, но бросил, потому, что даже самые элементарные примеры не работают. Теперь, после того, как заработал элементарный прием сообщений от браузера - можно и умной литературы немного почитать.
> Да, Вы правы - просто нет прав. Если запускать из-под sudo, то
> 80 порт слушает.
> Насчет книжек и Стивенса, в частности, - читал, но бросил, потому, что
> даже самые элементарные примеры не работают. Теперь, после того, как заработал
> элементарный прием сообщений от браузера - можно и умной литературы немного
> почитать.Что в примерах от Стивенса не заработало??
Нет, не заработало.
Еще вопрос:
Веб-браузер посылает запрос и мы должны ему ответить - послать страницу.
Я страницу посылаю так:
char* reply = "Content-type: text/html\n\n<HTML><BODY>\n<P>Hello!</P>\n</BODY></HTML>\n";
write(sock, reply, strlen(reply));Но браузер ее не отображает.
1. Я не туда пишу.
2. Я не добавил заголовок к ответу типа: "HTTP 200 OK"
Здесь приведен участок кода, в котором программа слушает http-порт и читает данные, которые посылает ей браузер, если ему указать localhost, и затем делается попытка послать ответ браузеру (char* reply = "HTTP/1.1 403 Forbidden\r\n\0";)if (!listen(sock, 5)) {
ret_addr = accept(sock, (struct sockaddr*)&addr_info, (socklen_t*)&add_len);
n = read(ret_addr, buf, 255);
buf[255] = '\0';
n = write(sock, reply, strlen(reply));
cout << errno << endl;
cout << buf << "\n";
}чтение возвращает -1, errno=57 - Socket is not connected.
Что я делаю не так?
> Что я делаю не так?Как минимум не проверяете, что вернул accept()
Хорошее выражение: "Смотрю в книгу и вижу..."
В read() я читаю из ret_addr, а писать пытаюсь в socket.
Теперь вопрос по процессам:
Предположим, я хочу, чтобы родительский процесс моей программы принимал запросы от клиентов (браузеров), и создавал для каждого из них дочерний процесс, который, в свою очередь, обрабатывал запрос и завершался, т.е.:int main (int argc, char * const argv[]) {
sock = socket(AF_INET, SOCK_STREAM, 0);
bind(sock, (struct sockaddr*)&addr_info, sizeof(addr_info));
listen(sock, 1);
for (;) {
ret_addr = accept(sock, (struct sockaddr*)&addr_info, (socklen_t*)&add_len);
if (!fork()) { // child
close(sock);
// read
do {
n = read(ret_addr, buf, BUF_SIZE);
buf[n] = '\0';
cout << buf;
}
while (n==BUF_SIZE);
reply = "Child process: ";
out << getpid();
reply+= out.str();
n = write(ret_addr, reply.c_str(), reply.length());
if (errno) {perror("Errno: ");}
exit(0);
}
else { // parent
close(ret_addr);
}
}
close(sock);
return 0;
}Такая программа обрабатывает первый запрос нормально, второй запрос обрабатывает, но в errno записывает код ошибки Bad file descriptor, ну а третий запрос вообще не обрабатывает.
Что тут не так?
Ваш код неполон (как минимум, в нём есть необъявленные переменные) и к тому же содержит синтаксические ошибки. Проявите хоть каплю уважения к тем, к кому обращаетесь за помощью и не заставляйте их гадать, где же тут что имелось в виду и как же это "по идее" должно было бы работать. Выкладываете код, значит он должен компилироваться и работать, как вы описываете. И используйте тег [ code] (без пробела перед code), а то же совершенно невозможно это читать.Касательно вашего творчества: трудно сказать, почему это не работает, потому что вы много чего делаете неправильно: не те проверки, не там проверки... Для начала это всё нужно переписать, а уж потом оллаживать.
P.S. Вам не показалось довольно странным, что так много людей упорно советуют вам что-нибудь почитать об этом всём? Не документацию (хотя это тоже нужно, конечно), а какую-нибудь книжку, учебник... Поверьте, это не совпадение.
P.P.S. И ради бога, переименуйте уже эту переменную ret_addr. Ну никакой это не адрес. Вводит же в заблуждение. Нельзя же переменные от балды называть.
Да я пытился читать Стивенса - говорю же Вам, что на первом же примере засыпался. Я не могу и не умею читать книжки. Я все пытаюсь написать руками - на что трачу много ненужного времени.
Код исправлю, в теги буду писать на будущее.
В данном примере хотел представить идею разделения процессов: кто куда идет - код не компилируем, все убрал из него только чтобы общую картину показать.
Мне дали совет использовать библиотеку boost.asio - но я не могу слинковать libboost_system
Как ни прискорбно - но с boost у меня тоже не срослось.
Итак, вернемся к самописному варианту tcp-сервера.
Как утверждает Стивенс (и не зря утверждает), "сервер" начинает слушать протокол и порт, который мы ему "натравили":
listen();
И далее принимает(или "засыпает", если очередь входящих сообщений пуста) входящие запросы от "клиента" (в нашем случае - веб-браузера):
accept();(Дальше мои мысли)
Следовательно, как только accept() вернул нам валидный дескриптор:
if ( (session=accept(socket, buf, buf_len))!=-1 ) {/*valid handler*/}
мы можем создавать дочерний поток(процесс), который обработает запрос и завершится:
listen();
for (;;) {
accept();
if ( (session=accept(socket, buf, buf_len))!=-1 ) {
if(!fork()) {/*child*/do smth; exit(0);}
else {/*parent*/ close(session);}
}
}Теперь скажите мне, почему session закрывается родителем?
Я, конечно, ноль в многопоточности, однако из прочитанной мной литературы о процессах сделал вывод, что с момента
fork()
и родитель, и его потомок начинают работать одновременно в одном и том же адресном пространстве (за последнее не ручаюсь). Как же потомок сможет читать/писать в session, если родитель, согласно логике программы, сразу же закроет дескриптор?
Подскажите, пожалуйста, в каком месте нарушена логика?
>[оверквотинг удален]
>
вы невнимательно читали Стивенса. У дескриптора есть счётчик ссылок, соединение будет закрыто по закрытию последнего дескриптора, ссылающегося на это соединение. Адресное пространство после форк скопировано от родителя, но не одно и то же, табличка с влиянием форк/ехес/ехit на дескрипторы/мутексы/етс также приведена у Стивенса.
Если честно - я его вообще не читал. Но раз Вы утверждаете, что у него есть описание взаимодействия процессов с дескрипторами, я обязательно посмотрю, что он пишет.
Из прослушанных мною лекций по многопоточности я тоже сделал умозаключение, что используется счетчик ссылок, но не был в этом уверен. Теперь Вы укрепили меня в моем мнении. Спасибо.
> Если честно - я его вообще не читал. Но раз Вы утверждаете,
> что у него есть описание взаимодействия процессов с дескрипторами, я обязательно
> посмотрю, что он пишет.
> Из прослушанных мною лекций по многопоточности я тоже сделал умозаключение, что используется
> счетчик ссылок, но не был в этом уверен. Теперь Вы укрепили
> меня в моем мнении. Спасибо.Закрытие дескрипторов при создании дочернего процесса для каждого соединения описано в главе про разработку серверных приложений по различным моделям книги "Разработка сетевых приложений UNIX", табличка с влиянием fork/exec/exit на различные ресурсы в отдельном труде "Взаимодействие процессов UNIX".
ребят, ну откуда вы такие беретесь.
не можете читать книжки, смотрите примеры.
http://tinyhttpd.cvs.sourceforge.net/viewvc/tinyhttpd/tinyht...
> ребят, ну откуда вы такие беретесь.
> не можете читать книжки, смотрите примеры.
> http://tinyhttpd.cvs.sourceforge.net/viewvc/tinyhttpd/tinyht...читать книжки мы не умеем, пользоваться гуглом тоже http://stackoverflow.com/questions/176409/how-to-build-a-sim...
>> То есть, тот факт, что два разные программы, процессы, нити,..., не могут
>> слушать
>> один и тот же порт, вам не известен? Где Вы говорите
> То что этого не умеет ванильный линукс, вовсе не значит, что этого
> не умеют другие системы.А кто умеет то? Я ничо мне интересно просто...
> А кто умеет то? Я ничо мне интересно просто...Как минимум все BSD семейство и AIX, ну и если верить манам HPUX (сам про него хз)
>> А кто умеет то? Я ничо мне интересно просто...
> Как минимум все BSD семейство и AIX, ну и если верить манам
> HPUX (сам про него хз)Чёто ты кривого чешешь! :)Ссылочку кинь?
>[оверквотинг удален]
> ret_addr = recv(sock_id, buf, 256, 0);
> // ret_addr = accept(sock_id, (struct sockaddr*)&addr_info, (socklen_t*)&add_len);
> return 0;
> }
> Но я так понимаю, что это совсем не в ту сторону. Единственное,
> что тут правильно - это socket().
> Пожалуйста, подскажите схему приложения-реализации данной задачи, что за чем следует,
> а я постараюсь по Вашей схеме написать реализацию.
> Заранее благодарен.
> Алексей
Теперь встал еще один вопрос: Как отправить заголовок браузеру? Т.е.:
в php ч/з header(), а в с++ - ?
Начнем с самого простого, без чего, якобы, браузер вообще не сможет прочитать содержимое ответа (но, тем не менее, читает) - Content-length:
string str = "<html>...</html>";
string reply = "Content-length: "+str.length();
reply+= "\r\n\r\n"; // end of header
reply+= str;
Здесь все отлично - браузер отображает содержимое документа, причем сам заголовок в теле документа не отображается.
Далее хочу отправить кодировку в документ, т.к. документ у меня в cp-1251, а браузер - в utf-8 (default):
Какой же параметр устанавливает кодировку в заголовке?
google.com -> http header rfc -> http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
Читаю:
14.2 Accept-Charset
Accept-Charset: iso-8859-5, unicode-1-1;q=0.8
14.17 Content-Type
Content-Type: text/html; charset=ISO-8859-4
Пишу:
string str = "<html>...</html>";
string reply = "Accept-Charset: cp-1251"; //string reply = "Content-Type: text/html; charset=cp-1251";
reply+= "\r\n\r\n"; // end of header
reply+= str;
Ни один из этих вариантов браузер не считает за заголовки и выводит их в основное тело документа.
Как правильно и какие заголовки посылать браузеру?
Проблемы с кодировкой наблюдались у меня в случае, когда я делал так:wget google.com/index.html
И далее использовал полученный файл, как документ.
Если же я самостоятельно напишу документ в текстовом редакторе по всем правилам указания кодировок - таких проблем не возникает.Теперь усложним задачу:
Для начала заметим, что значение дескриптора socket и accept в программе:
sock = socket(...);//4
session = accept(sock, ...); // 5Теперь запустим нашу программу как демон из другой програмы:
if(!fork()) execl("myprog", argv);Наша программа успешно запустилась и ждет входящих сообщений, но не обрабатывает их (т.е. по-просту, не работатет).
И почему-то указанные выше sock и session на 1 меньше:
sock = socket(...);//3
session = accept(sock, ...); // 4Что тут не так?
> И почему-то указанные выше sock и session на 1 меньше:
> sock = socket(...);//3
> session = accept(sock, ...); // 4
> Что тут не так?да, а ведь должно быть меньше на три дескриптора, ведь это демон.
Неправильный подход к реализации: я запускал процесс из другой программы, а все делается проще:
int main () {
if (fork()) exit(0);
/* daemon code here*/
return 0;
}