Мы — долго запрягаем, быстро ездим, и сильно тормозим.
www.lissyara.su —> статьи —> FreeBSD —> Programming —> Socket сервер на FreeBSD.

Socket сервер на FreeBSD.

Автор: Fastman.


 По ходу работы необходимо было из клиента написанного под Windows запускать скрипты на Unix машине (в качестве которой сейчас у меня FreeBSD 6.1). Пишу в основном на C/C++ поэтому решил не изменять традициям. Опыта программирования под Unix практически не было, поэтому буду рад любым замечаниям. В статье будет показан  "каркас" серверного приложения под FreeBSD. Инструменты:Mc + mcedit + c++ :))) На самом деле сейчас этот кусок проекта очень сильно потолстел, и обзавелся кучей фишек, однако пускай каждый сам решает что может быть прицепленно к этому "каркасу".
Поехали :)
Чтобы не изобретать велосипеды, нарыл в интернете пример небольшой, и на его основе сваял ниже показанный код. Что мы должны получить в итоге ? TCP-демон, который будет слушать определенный порт, принимать данные, и отвечать клиенту. Для обслуживания TCP-демона, будем использовать механизм fork() который создает процесс-потомок, который отличается от родительского только значениями PID (идентификатор процесса) и PPID (идентификатор родительского процесса).
tcp_daemon.cpp:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <syslog.h>
#include <signal.h>

#include <vector>
#include <string>
#include <fstream>
#include <iostream>

using namespace std;

class tcpdaemon 
{
    protected:
	void daemonize();
	void mainloop();
	void operate(int fd);
	static void sighandler(int signum);

    public:
	void exec();
};

void tcpdaemon::sighandler(int signum)
{
    waitpid(0, 0, WNOHANG);
}

void tcpdaemon::daemonize() 
{
    int pid;
    struct sigaction sa;

    cout << "Server start ok ! " << flush;
    pid = fork();

    switch(pid)
    {
	case 0:
	    setsid();
	    chdir("/");

	    close(0);
	    close(1);
	    close(2);

	    memset(&sa, 0, sizeof(sa));
	    sa.sa_handler = &sighandler;
	    sigaction(SIGCHLD, &sa, 0);

	    openlog("tcp_daemon", 0, LOG_USER);
	    mainloop();
	    closelog();

	    exit(0);

	case -1:
	    cout << "fork() error" << endl;
	    break;

	default:
	    cout << "ok. PID=" << pid << endl;
	    break;
    }
}

void tcpdaemon::mainloop()
{
    int sockfd, fd;
    struct sockaddr_in sa;
    socklen_t n;

    sockfd = socket(PF_INET, SOCK_STREAM, 0);

    if(sockfd != -1)
    {
	memset(&sa, 0, sizeof(sa));

	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = htonl(INADDR_ANY);
	sa.sin_port = htons(1667);

	if(bind(sockfd, (struct sockaddr *) &sa, sizeof(sa)) != -1)
	{
	    while(1) {
		if(!listen(sockfd, 5))
		{
		    n = sizeof(sa);
		    if((fd = accept(sockfd, (struct sockaddr *) &sa, &n)) != -1)
		    {
			syslog(LOG_NOTICE, "connection from %s", inet_ntoa(sa.sin_addr));

			if(!fork()) 
			{
			    operate(fd);
			}
		    }
		}
	    }
	}
    }
}

void tcpdaemon::operate(int fd)
{
    char c;
    bool finished;
    string cmd, answer;

    finished = false;

    write(fd, answer.c_str(), answer.size());

    while(!finished) 
    {
	cmd = answer = "";

	while(!finished) {
	    finished = read(fd, &c, 1) != 1;

	    if(c == '\n') break; else
	    if(c != '\r') cmd += c;
	}

	if(!finished && !cmd.empty())
	{
	    if(cmd == "info")
	    {
                answer = "First Socket SERVER :)";
	    } 
	    else if(cmd == "version")
	    {
		answer = "version 1.0";
	    } 

	    else if(cmd == "quit")
	    {
		shutdown(fd, 2);
		close(fd);
		finished = true;
	    }

	    else
            {
	    
	    /*
            int i  = system(cmd.c_str());
	    if(i == -1)
	    {
	    answer = "err";
	    }
	    else answer = "ok";
            */
	    }	

	}

	if(!finished && !answer.empty())
	{
	    answer += "\n";
	    write(fd, answer.c_str(), answer.size());
	}
    }

    exit(0);
}

void tcpdaemon::exec() 
{
    daemonize();
}

int main()
{
    tcpdaemon d;
    d.exec();
    return 0;
} 

По хорошему, конечно, нужно разносить в разные файлы определения членов класса, их реализацию и функцию main(), что в последствии и было сделанно.
Чтобы после исправления кода вручную не писать команду компилирования делаем маленький скрипт:
make.sh:

#!/bin/sh
killall tcp_daemon
c++ tcp_daemon.cpp -o tcp_daemon
./tcp_daemon

После запуска, получаем:

# ./make.sh
No matching processes were found
Server start ok ! ok. PID=45635

Естесственно пока у нас не запущен сервер то скажет No matching processes were found, зато в следующий раз после того как мы внесем какие нибудь изменения после запуска этого скрипта прибъется сервер,скомпилируется и запустится.
Теперь можно протестировать на работоспособность всего этого хозяйства:

>telnet localhost 1667
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
info
First Socket SERVER :)
version
version 1.0
quit
Connection closed by foreign host.

Т.е в данный момент введя команды info, version и quit мы убедились что сервер наш работает и адекватно реагирует :) Если присмотреться к коду сервера, то увидим что если комманда введенная не соответствует ни одной из тех что мы ввели то будет выполняться другой кусок кода, у меня там закомментированы следующие строки:

/*
int i  = system(cmd.c_str());
if(i == -1)
{
  answer = "err";
}
else answer = "ok";	
*/

Функция system(const char * string), выполняет команды, указанные в string. Поэтому если вы раскомментируете эти строки(не забыв после этого make.sh запустить:)) и сделаете так:

>telnet localhost 1667
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
uname -a > uname
ok
quit
Connection closed by foreign host.

то в корне у вас появиться файл uname c содержанием в частности у меня:

FreeBSD myserver 6.1-RELEASE FreeBSD 6.1-RELEASE #3: Wed Aug 16 12:43:36 EEST 2006 root@myserver:/usr/obj/usr/src/sys/SMP i386

Будте аккуратней :) Никаких проверок нет, поэтому то же пресловутое rm /-rf выполнится на ура :)
То же самое можете проверить с любого компьютера с Windows в сети.
Комментарии, если есть предложения/пожелания - в форум. Если будет интересно, сваяю статью как ко всему этому хозяйству подступиться из Win для автоматического запуска скриптов/комманд.



размещено: 2007-04-24,
последнее обновление: 2007-04-24,
автор: Fastman


fr33man, 2007-04-25 в 15:06:52

Не люблю C++... Лучше C.

IMHO.

Fastman, 2007-04-25 в 15:34:20

Эксперементировал. Можно и на С.

ALex_hha, 2007-04-26 в 18:26:28

Нее, уж лучше на ассемблере

Dushes, 2007-04-27 в 18:51:41

ОГРОМНОЕ спасибо автору давно уже ищу что нить подобное ...

Dushes, 2007-04-27 в 18:55:13

А вообще примеры с коментариями по програмированию очень актуально ...

Fastman, 2007-04-27 в 19:01:54

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

un, 2007-05-04 в 21:52:44

Ждем того же, но под Win :)

dehun, 2007-05-04 в 23:32:16

А после того как клиент закрывает соединение и процесс умирает он в зомби не преврашяется( Ну всмысле чтобы хранить код завершения ) ?

На асме не кроссплатформенно =)

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

Fastman, 2007-05-04 в 23:43:21

dehun
Обращаем внимание на
void tcpdaemon::sighandler(int signum)
{
   waitpid(0, 0, WNOHANG);
}

и
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &sighandler;
sigaction(SIGCHLD, &sa, 0);

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

Процесс и поток - разные вещи.

Сервер выкладывать под винду не буду, тут FreeBSD :)
А вот про клиента и под Win и под FreeBSD - напишу.
Быстро не обещаю, работы много, но постараюсь.
И вообще потестил бы кто нить. Никто не сказал, что в принципе все что было написано выше работает гарантированно только под root-ом :)

ALex_hha, 2007-05-12 в 12:36:50

Не очень понял смысл этой программы, а чем вас ssh не устраивает?

ALex_hha, 2007-05-12 в 16:17:58

хм....
из клиента написанного под Windows запускать скрипты на Unix машине

Если ты мне расскажешь как использую ssh написать на C++ в VS8 примерно следующий функционал: клиент должен запускать 2-5 процессов одновременно на сервере (путем запуска обычного sh скрипта) которые могут работать в течении от 20мин до 3часов. При этом нужно иметь обратную связь с сервером(знать запустился процесс на выполнение или нет, завершился корректно или нет, с какими ошибками и.т.д. и.т.п.)
То я буду оч благодарен. Не шучу. Пиши статью в раздел.

миро, 2007-06-01 в 12:24:47

system не выполняет команд внутри этого tcpdaemon класса

           int i  = system(cmd.c_str());
       if(i == -1)
       {
       answer = "err";
       }
       else answer = "ok";

Уберите две "щщ" в слове Товарищщи!, 2007-07-29 в 12:47:50

БЛЯ

тормоз?, 2007-10-25 в 4:15:13

ssh -ladmin server /bin/sh

> клиент должен запускать 2-5 процессов одновременно на сервере
> (путем запуска обычного sh скрипта) которые могут работать
> в течении от 20мин до 3часов.

ssh -ladmin server /bin/ls
ssh -ladmin server /bin/ps
ssh -ladmin server /bin/df

> При этом нужно иметь обратную связь с сервером
> (знать запустился процесс на выполнение или нет, завершился
>  корректно или нет, с какими ошибками и.т.д. и.т.п.)

читать вывод, парсить ошибки.

Fastman, 2007-10-29 в 23:33:27

тормоз?
Блин... вы интересные ананимусы.... и что ты понаписывал то ? Мне вместе с клиентским софтом отдельно таскать ssh клиент ? или пихать в софт класс для работы с ssh в свое приложение? Это несерьезное решение. Это не пригодно для меня.
...
как использую ssh написать на C++ в VS8
читаем и понимаем.. что ничего конкретного не написано. Xоть в статью оформи хоть в форум напиши. Запускай через свой ssh mencoder и пускай он DV файло в mpeg4 жмет. Дает отчет о живучести о проценте выполнения и пускай лог пишет. Мне проще написать свое приложение пускай с нуля... пускай сначало коряво (оттещу научусь и доделаю) чем использовать нечто похожее на костыли.

tolik, 2008-06-04 в 16:35:37

Вообще-то, для таких целей есть inetd...

анонимус такой с претензией, 2008-08-20 в 21:27:55

для кого статья не ясно...
т.к. если человек знает что такое сокеты, ему эта статья не нужна. а для тех кто не знает(такие как я 2 часа назад) - смысл пялится на непонятные строчки?? или люди сами поняли что обозначает каждая строчка? я не верю что я здесь один такой тупой.
http://www.linuxhowtos.org/C_C++/socket.htm - первая ссылка в гугле и то лучше.

Seltsam, 2008-12-12 в 9:12:58

to анонимус такой с претензией : если тебе это не понятно, то и не читай! кому надо - тот знает! а впервые узнавать о сокетах и т.п. надо не из таких статей!
Извини за прямоту...
Интересующимся - есть вариант на Perl для TCP- и UDP- серверов, может кому будет и проще и интересней.
Автору - думаю можно использовать для таких целей UDP-сервер, в этом случае нет необходимости думать о форках и т.д.
В качестве клиента для винды можно написать простую формочку на C++ Builder, там есть компоненты для такой работы.
Сам давеча написал демон под UDP, который принимает инфу от виндовых клиентов при входе логоне/логофе юзера в систему и складывает всё это в БД MySQL. Ну а далее уж от фантазии зависит что делать с этой БД =)

v01d.cmd, 2009-01-07 в 10:07:09

Cпс за статью. Проверил на Mac OS X 10.5 пашет, подлюбым пользователем кроме Guest. Привелегии почти по умолчанию.

Тем кто в танке: статья образовательная, для нубов и скрипткидов. Понимаю что ssh надежней... напиши статью про это, все скажут СПС тебе, в т.ч. я =).

Dis, 2009-04-23 в 18:33:38

Спасибо за статью, помогла, правда писал на Си.

alky, 2009-10-03 в 2:55:03

>Блин... вы интересные ананимусы.... и что ты >понаписывал то ? Мне вместе с клиентским софтом >отдельно таскать ssh клиент ? или пихать в софт класс >для работы с ssh в свое приложение? Это несерьезное >решение. Это не пригодно для меня.
>...

ссш по любому лучше. цель выполнить команды удаленно? он для этого и создан и к тому же безопасен.
а почему бы не таскать если это УЖЕ ГОТОВО?
или если интегрировать - www.libssh.org..
ЭТО ЖЕ ОСНОВНОЙ ПРИНЦИП ОПЕН СОРС - не строить велосипеды..
ну а если про сокеты... нет ни одного коммента к коду!(типа кто-то врубился во все. ага щас.)
http://www.linuxhowtos.org/C_C++/socket.htm  - тут реально разжевано, спасибо анониму за ссылку.

Olorin, 2010-02-10 в 17:50:35

Рекомендую: opennet.ru

ostrik, 2011-02-24 в 18:08:39

Хорошо бы еще упомянуть настоящего автора этого кода :)
http://konst.org.ua/ru/writings
http://www.codenet.ru/progr/cpp/demons.php

Не хорошо выдавать чужое за свое.

ivan, 2011-07-19 в 9:48:52

Трабл.
Открываем телнетом два конекта к сокету и выполняем команду (к примеру ls) - результат выполнетия из обеих сесий выводится на первую. При этом результат выполнения внутреней комманды (к примеру version) выводится в ту сесию с которой выполнял.

pinger, 2011-12-27 в 12:06:25

Под виндой в Cygwin собирается и работает, если закомментить строки с методами memset() и exit().
Спасибо!

Cpp_dev, 2013-10-21 в 11:12:19

Даже сейчас статья оказалась полезной. Нигде не нашел небольшего, но понятного кода, чтоб во всем разобраться, повезло только здесь. Теперь немного разбрался с тем, как создать демона. Большое спасибо за статью!
Конечно коменты в коде были бы не лишни)

Cpp_dev, 2013-10-21 в 16:03:49

Можно еще заменить system() на popen(), чтобы вместо ок и err возвращать результаты команды, что очень удобно.

Den, 2013-12-14 в 14:32:17

Статья полезна только в качестве примера для программирования. Практического применения ей нет. Автоматизацию даёт cron (уж точно не ручной запуск скриптов:), а удаленное подключение через ssh куда более удобно (из под win в тч). Автор, видимо, в первый раз FreeBSD увидел )))). На момент написания статьи был еще совсем зеленым )))

aa, 2015-07-09 в 23:32:33

Не компилится...
sh ./make.sh
tcp_daemon.cpp: In member function ‘void tcpdaemon::daemonize()’:
tcp_daemon.cpp:53:31: error: ‘memset’ was not declared in this scope
     memset(&sa, 0, sizeof(sa));
                              ^
tcp_daemon.cpp:61:12: error: ‘exit’ was not declared in this scope
     exit(0);
           ^
tcp_daemon.cpp: In member function ‘void tcpdaemon::mainloop()’:
tcp_daemon.cpp:83:27: error: ‘memset’ was not declared in this scope
 memset(&sa, 0, sizeof(sa));
                          ^
tcp_daemon.cpp: In member function ‘void tcpdaemon::operate(int)’:
tcp_daemon.cpp:171:11: error: ‘exit’ was not declared in this scope
    exit(0);
          ^



 

  Этот информационный блок появился по той простой причине, что многие считают нормальным, брать чужую информацию не уведомляя автора (что не так страшно), и не оставляя линк на оригинал и автора — что более существенно. Я не против распространения информации — только за. Только условие простое — извольте подписывать автора, и оставлять линк на оригинальную страницу в виде прямой, активной, нескриптовой, незакрытой от индексирования, и не запрещенной для следования роботов ссылки.
  Если соизволите поставить автора в известность — то вообще почёт вам и уважение.

© lissyara 2006-10-24 08:47 MSK

Время генерации страницы 0.0511 секунд
Из них PHP: 36%; SQL: 64%; Число SQL-запросов: 77 шт.
Исходный размер: 44931; Сжатая: 11645