1. Вы находитесь в архивной версии форума xaker.name. Здесь собраны темы с 2007 по 2012 год, большинство инструкций и мануалов уже неактуальны.

Пишем "ДЕМОНА" своими руками

  1. Zik
    Итак целью нашей с Вами задачи встал вопрос написания сетевого демона для управления чем либо на Linux из под Windows с различных мест.

    В каждой реализации Linux всегда присутствует библиотека GNU libc. Мы не будем использовать объектно-ориентированное-программирование (ООП), а возьмем стандартный язык разработки под Linux - это Cи. Статья рассчитана на пользователей только начинающих изучать язык Си.

    Краткая справка:


    Разработка системы GNU началась 27 сентября 1983 года, когда Ричард Столлмэн опубликовал объявление о проекте в группах новостей net.unix-wizards и net.usoft.
    5 января 1984 года Столлмэн уволился из Массачуссетского технологического института с целью посвятить своё время написанию свободной операционной системы, а также для того, чтобы институт не мог претендовать на какие-либо права на исходный код системы. Первой программой GNU стал текстовый редактор Emacs.
    В настоящее время система GNU/Linux, более широко известная как просто Linux, достаточно распространена (особенно на рынке серверов) и является вполне завершённой. Она состоит из большого количества программ проекта GNU (в первую очередь системных утилит и GNU toolchain), ядра Linux - части системы, отвечающей за выполнение других программ, включающей драйверы устройств и т. п., - и множества других свободных программ.


    И так преступим к написанию сетевого ЭХО сервера с признаками "демонизма".
    Файл назовем daemon.c
    Код:
    cd / --переходим в корневой каталог
            mkdir myprogonlinux --создаем папку где будем писать демона
            cd myprogonlinux
            touch daemon.c --создаем файл
            vi daemon.c
    и так файл daemon.c

    Код:
    #include <stdio.h> //--список объявлений и используемых нами готовых библиотек Cи
            #include <string.h> //--подробно о каждой библиотеке можно узнать в man
            #include <sys/stat.h> //--например man stdio и.т.д
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/time.h>
            #include <sys/socket.h>
            #include <sys/un.h>
            #include <unistd.h>
            #include <signal.h>
            #include <sys/wait.h>
            #include <resolv.h>
            #include <errno.h>
    Любая программа на Си (если это не вспомогательный модуль где описываются выполняемые универсальные функции)начинается с главной процедуры или функции main

    Код:
    #include <stdio.h> 
            #include <string.h> 
            #include <sys/stat.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/time.h>
            #include <sys/socket.h>
            #include <sys/un.h>
            #include <unistd.h>
            #include <signal.h>
            #include <sys/wait.h>
            #include <resolv.h>
            #include <errno.h>
     int Daemon(void) // --это объявление функции для нашего демона то есть реализация ЭХО сервера
    
            int main(int argc, char* argv[]) //--это наша главная процедура с которой начинается программа
            {
              pid_t parpid;
              if((parpid=fork())<0) //--здесь мы пытаемся создать дочерний процесс главного процесса (масло масляное в прямом смысле) 
                {                   //--точную копию исполняемой программы
                 printf("\ncan't fork"); //--если нам по какойлибо причине это сделать не удается выходим с ошибкой.
                 exit(1);                //--здесь, кто не совсем понял нужно обратится к man fork
                }
              else if (parpid!=0) //--если дочерний процесс уже существует
              exit(0);            //--генерируем немедленный выход из программы(зачем нам еще одна копия программы)
              setsid();           //--перевод нашего дочернего процесса в новую сесию
              Daemon();           //--ну а это вызов нашего демона с нужным нам кодом (код будет приведен далее)
              return 0;
    Я думаю многие после данного объяснения ничего не поняли. Поясняю на пальцах Любая программа на Си начинает исполнятся с главной процедуры main.
    При запуске программы создается процесс (на время работы программы). Мы просто создаем дочернюю копию этого процесса и переводим его в новую сессию, что бы он не блокировал текущую вашу сессию.
    Для лучшего понимания (не поленитесь) перепишем выше приведенный код и сделаем два режима работы "демон","не демон"

    Код:
    #include <stdio.h> 
            #include <string.h> 
            #include <sys/stat.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/time.h>
            #include <sys/socket.h>
            #include <sys/un.h>
            #include <unistd.h>
            #include <signal.h>
            #include <sys/wait.h>
            #include <resolv.h>
            #include <errno.h>
    
            int Daemon(void)
    
            int main(int argc, char* argv[])
            {
              pid_t parpid;
    
              if (argc < 2)
              {
               printf("Usage ./daemon -d for daemon or ./daemon -i for interactive\n");
               exit(1);
              }
              if (strcmp(argv[1],"-i")==0)
              Daemon();
              else if (strcmp(argv[1],"-d")==0)
              {
               if((parpid=fork())<0) 
                {                  
                 printf("\ncan't fork");
                 exit(1);               
                }
              else if (parpid!=0)
              exit(0);           
              setsid();          
              Daemon();
              }
              else
              {
              printf("Usage ./daemon -d for daemon or ./daemon -i for interactive\n");
              exit(1);
              }          
              return 0;
            }
    то есть программу мы будем запускать с ключами "-d",как демон и "-i", как не демон.

    Итак преступаем к написанию нами функции Daemon()

    Код:
    #include <stdio.h> 
            #include <string.h> 
            #include <sys/stat.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/time.h>
            #include <sys/socket.h>
            #include <sys/un.h>
            #include <unistd.h>
            #include <signal.h>
            #include <sys/wait.h>
            #include <resolv.h>
            #include <time.h>
            #include <errno.h>
    
            #define PORT_CON 6823 //--определяем порт который будет прослушивать наш эхо сервер
    
            struct sockaddr_in client_name;//--структура sockaddr_in клиентской машины (параметры ее нам неизвестны. Мы не знаем какая машина к нам будет подключаться)
            int size = sizeof(client_name);//--размер структуры (тоже пока неизвестен)
            int client_socket_fd;          //--идентификатор клиентского сокета
                                           //--вобще теория сокетов более подробно будет рассмотрена в следующей статье
    
            char sockbuff[1024]; //--наш буфер обмена информацией с клиентом
            time_t now;
            struct tm *ptr;
            char tbuf[80];
    
            int Daemon(void)
            char* getTime();
    
            char * getTime() //--функция определения времени в нужном формате
            {
             char *ret;
             ret=(char*)malloc(100);
             bzero(ret,100);
             time(&now);
             ptr = localtime(&now);
             strftime(tbuf,80,"%Y-%B-%e %H:%M:%S",ptr);
             ret=tbuf;
             return (ret);
            }
    
            int main(int argc, char* argv[])
            {
              pid_t parpid;
    
              if (argc < 2)
              {
               printf("Usage ./daemon -d for daemon or ./daemon -i for interactive\n");
               exit(1);
              }
              if (strcmp(argv[1],"-i")==0)
              Daemon();
              else if (strcmp(argv[1],"-d")==0)
              {
               if((parpid=fork())<0) 
                {                  
                 printf("\ncan't fork");
                 exit(1);               
                }
              else if (parpid!=0)
              exit(0);           
              setsid();          
              Daemon();
              }
              else
              {
              printf("Usage ./daemon -d for daemon or ./daemon -i for interactive\n");
              exit(1);
              }          
              return 0;
            }
    
            int Daemon(void)
            {
              FILE *logfile; //--лог для подключившихся клиентов причем для каждого будет свой
              int socket_fd,nbytes; //--объявляем идентификатор сокета нашего сервера
              char host[20];
              char *namlog;
              void sig_child(int);//--объявление функции ожидания завершения дочернего процесса
              pid_t pid;
              struct sockaddr_in name;//--структура sockaddr_in для нашего сервера на сей раз ее придется заполнить
              namlog=(char*)malloc(25);
              socket_fd=socket(PF_INET,SOCK_STREAM,0); //--создаем сокет сервера в данном случае TCP, если бы мы использовали флаг SOCK_DTGRAM то получили бы сокет UDP
              name.sin_family=AF_INET; //--говорим что сокет принадлежит к семейству интернет
              name.sin_addr.s_addr=INADDR_ANY; //--наш серверный сокет принимает запросы от любых машин с любым IP-адресом
              name.sin_port=htons(PORT_CON); //--и прослушивает порт 6823
              if(bind(socket_fd, &name, sizeof(name))==-1) //--функция bind спрашивает у операционной системы,может ли программа завладеть портом с указанным номером
              {
                perror("bind"); //--ну если не может,то выдается предупреждение 
                exit(0); //--соответственно выход из программы
              }
              listen(socket_fd,20); //--перевод сокета сервера в режим прослушивания с очередью в 20 позиций
              for(;;) //--бесконечный цикл (как так?... спросите ВЫ. У нас же сервер,который должен постоянно принимать и обслуживать запросы и работать пока его не погасят насильно)
              {
                 signal(SIGCHLD,sig_child); //--если клиент уже поработал и отключился ждем завершение его дочернего процесса
                 client_socket_fd = accept (socket_fd,&client_name,&size); //--подключение нашего клиента
                 if (client_socket_fd>0) //--если подключение прошло успешно
                 {
                   if ((pid=fork())==0) //--то мы создаем копию нашего сервера для работы с другим клиентом(то есть один сеанс уде занят дублируем свободный процесс)
                      {
                          inet_ntop(AF_INET,&client_name.sin_addr,host,sizeof(host));--в переменную host заносим IP-клиента
                          bzero(namlog,25);
                          strcpy(namlog,host);
                          strncat(namlog,"\0",1);
                          strcat(namlog,".log"); //--для каждого соединения делаем свой лог
                          if(fopen(logfile,namlog,"a+")!=NULL); //--создаем лог файл подключившегося клиента
                          {
                          fprintf(logfile,"%s Connected client:%s in port: %d\n",getTime(),host,ntohs(client_name.sin_port));
                          fflush(logfile);
                          fclose(logfile);
                          }                
                          do
                          {
                           bzero(sockbuff,1024); //--чистим наш буфер от всякого мусора
                           nbytes=read(client_socket_fd,sockbuff,1024); //--читаем из в буфер из клиентского сокета
                           strncat(sockbuff,"\0",1); //--незабываем символ конца строки
                           if(fopen(logfile,namlog,"a+")!=NULL);
                           {
                            fprintf(logfile,"%s Client send to Server:%s\n",getTime(),sockbuff);
                            fflush(logfile); //--эту запись в лог файл конечно можно оформить ввиде отдельной функции
                            fclose(logfile); //--даную функцию вы должны написать уже сами для тренировки
                           }
                           sendto(client_socket_fd,sockbuff,strlen(sockbuff),0,&client_socket_fd,size); //--и отсылаем полученую информацию назад               
                           if(fopen(logfile,namlog,"a+")!=NULL);
                           {
                            fprintf(logfile,"%s Server answer to client:%s\n",getTime(),sockbuff);
                            fflush(logfile);
                            fclose(logfile);
                           } 
                           }
                          while(nbytes > 0 && strncmp("bye",sockbuff,3)!=0); //--выполняем цикл пока клиент не закроет сокет или пока не прилетит сообщение от клиента "bye"
                          if(fopen(logfile,namlog,"a+")!=NULL);
                          {
                          fprintf(logfile,"%s Close session on client:%s\n",getTime(),host);
                          fflush(logfile);
                          fclose(logfile);
                          }
                          close(client_socket_fd); //--естествено закрываем сокет
                          exit(0); //--гасим дочерний процесс
                       }
                       else if (pid > 0)
                       close(client_socket_fd);
                  }
              }
            }
    
            void sig_child(int sig) //--функция ожидания завершения дочернего процесса
            {
             pid_t pid;
             int stat;
             while ((pid=waitpid(-1,&stat,WNOHANG))>0)
             {
              }
              return;
            }
    
    

    Ну вот и все.....Демон готов к работе. Прежде чем использовать....нужно откомпилировать и убрать возможные ошибки
    Итак.....компиляция
    Код:
    //для RedHat,FreeBCD,Slackware,OpenSuSe
             cd /
             cd myprogonlinux
             gcc daemon.c -g -lnsl -lresolv -o daemon
    Код:
    //для Solaris
             cd /
             cd myprogonlinux
             gcc daemon.c -g -lnsl -lsocket -o daemon
            //у Вас должен появиться скомпилированый выполняемый файл daemon
            //запускаем в режиме демона
            ./daemon -d
            //проверяем
             pgrep daemon
             3102
            //тушим
             pkill daemon
            //запускаем в интерактивном режиме
             ./daemon -i
             //Ну и конечно обнаруживаем что наша сессия зависла поможет только Ctrl+C
    Для проверки нашего демона запускаем его с ключем -d и в любой программной оболочки типа дельфи или С++Builder пишем простого клиента


    Код:
    //-----------------------------------------------------
    
            #include <vcl.h>
            #pragma hdrstop
    
            #include "Unit1.h"
            //-------------------------------------------------------       #pragma package(smart_init)
            #pragma resource "*.dfm"
            TForm1 *Form1;
            //---------------------------------------------------------
            __fastcall TForm1::TForm1(TComponent* Owner)
                    : TForm(Owner)
            {
            }
            //---------------------------------------------------------
    
            void __fastcall TForm1::ClientSocket1Read(TObject *Sender,
                  TCustomWinSocket *Socket)
            {
              AnsiString S="";
              S=Socket->ReceiveText();
              Memo1->Lines->Add(S);
            }
            //----------------------------------------------------------
    
            void __fastcall TForm1::FormCreate(TObject *Sender)
            {
             ClientSocket1->Host=Edit1->Text;
             ClientSocket1->Port=StrToInt(Edit2->Text);
             ClientSocket1->Open();
            }
            //---------------------------------------------------------
    
            void __fastcall TForm1::Button1Click(TObject *Sender)
            {
             ClientSocket1->Socket->SendText(Edit3->Text);
            }
            //--------------------------------------------------------

    Ну форму приводить не буду. Кто знаком с Дельфи и С++Builder тот разберется что к чему и раставит компоненты как будет удобно


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

    Шевелёв Денис <[email protected]>
     
    1 человеку нравится это.
  2. Подскажите пожалуйсто при помощи какой программы лучше в шелл-меню входить. желательно со ссылкой на скачку.