Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

exec и перенаправление в буфер/сокет + exec cd

53K
02 февраля 2010 года
nicolaus2
13 / / 02.02.2010
Всем здравствуйте, уже несколько дней не могу разобраться с кое-чем, а спросить, пока что, не у кого. И так, знатоки, есть две проблемы.

1. Есть сервер и клиент на основе TCP/IP сокетов. Сервер может обрабатывать запросы cat, ls, cd. Команды на сервере запускаются посредством функций семейства exec. В этом проблема и состоит: есть два терминала, в одном - сервер, во втором клиент делает запрос, скажем на ls, информация должна отразиться в терминале клиента.

Как перенаправить вывод на терминал именно клиента? Так как по умолчанию exec вывод происходит на stdout, которым является терминал сервера. Или хотя бы напрямую в массив, а не через файл?

Манипулирование с помощью dup2() результатов не принесло(возможно что то не так делал).

2. Мы выполняем команды exec путём вызова исполняемых файлов из /bin, но там нету файла <cd> (для открытия директорий). Есть ли аналог для отрытия директорий при выполнении exec, если да, то какой.


Буду благодарен за подсказки и советы. Спасибо.
43K
02 февраля 2010 года
loki231
76 / / 27.09.2009
И чего только люди не придумают!

Многое в постановке задаче осталось за кадром, но попробуем дедуктивным методом составить общую картину.

Раз были упомянуты сокеты и TCP/IP, то клиент и сервер работают на разных машинах, сколько каналов и как они организованы остаётся неизвестным. Предположим что взаимодействие производится по одному каналу.

Тогда так.

Код:
int sock; // Обозначим так межпроцессный канал на стороне сервера.

if (fork()==0) {
     dup2 (sock, 1);   // stdout в sock
     dup2 (sock, 2);   // stderr туда же.
                       // теперь 1, 2 и sock - одно и тоже. То что exec() будет выдавать на stdout и stderr будет выводиться в sock.
            // Если dup2 (sock, 0), тогда то, что будет приходить с клиентской
            //  машины, попадёт на stdin exec(). Если, конечно, его не вычитает
            // раньше родительский процесс. И здесь race conditioning.
     exec (cmd);
}


cd стоит особняком, это не внешняя программа, а встроенная команда sh, выполняя которую, sh вызывает функцию chdir (const char *). Придётся серверу разбирать команды запросов и если это "cd some/dir/here", то выполнять самому chdir ("some/dir/here").
53K
02 февраля 2010 года
nicolaus2
13 / / 02.02.2010
Вах, ну прям магия какая то ... я так же раз 20 пробовал писать - единственное отличие что тогда ещё не было fork()-а. Потом ввёл fork() и с dup2() уже и не пробовал. Может, действительно, проблема была в этом ...

С чдиром пока не разбирался.

Спасибо, loki231.

Тогда ещё один вопрос: как считаете, команду и директорию/файл для открытия стоит передавать по отдельности send-ом или одной кашей "команда_директория/файл", а на сервере уже разбирать где какая команда, а где директория/файл?
34K
03 февраля 2010 года
muturgan
96 / / 01.10.2009
Передавайте в одной команде, конечно. Зачем Вам париться с повторным приёмом сообщения? Если сообщение не дойдёт, то пусть уж полностью :)
53K
03 февраля 2010 года
nicolaus2
13 / / 02.02.2010
Ну вот, всё работает отлично .. почти отлично, есть одно "но".

Когда делаю chdir(), а потом ls, то происходит следующее: по умолчанию, на сервера, стоит та директория где и находится сам код сервера и откуда я его запускаю, при смене директории и последующих ls, сервер возвращает то список файлов старой директории, то список файлов новой, изменённой директории, чередуя их. Если изменю директорию два раза, то это "чередование" будет состоять из списка файлов из трёх директорий.

На всякий случай код cd:
 
Код:
if(strcmp("cd", command) == 0)
   {
      int test = chdir(element);
      send(clntSocket, "Ok", 3, 0);
   }

В command-е сохраняется сама команда, возможные варианты - cat, ls, cd. В element сохраняется директория для изменения.

Подскажите, пожалуйста, в чём может быть дело.
43K
04 февраля 2010 года
loki231
76 / / 27.09.2009
Я бы предположил, что не чистится приёмный буфер. Команды накапливаются в нём и каждый раз, при поступлении новой команды выполняется некоторая последовательность команд, полученных ранее. Аналогично с буфером передачи.
53K
04 февраля 2010 года
nicolaus2
13 / / 02.02.2010
Цитата: loki231
Я бы предположил, что не чистится приёмный буфер. Команды накапливаются в нём и каждый раз, при поступлении новой команды выполняется некоторая последовательность команд, полученных ранее. Аналогично с буфером передачи.



Хорошая версия, спасибо, но команды ls и cat без cd нормально работают. И, если бы буфер не чистился, то глюки были бы со всеми командами, а не только с ls при cd. Да и нету там, в буфере, команды смены директории на директорию что была по умолчанию. Тоесть, если я меняю chdir("/"), то в буфере нету команды, чтобы изменить директорию обратно, на ту что была с самого начала.

53K
10 февраля 2010 года
nicolaus2
13 / / 02.02.2010
Up. Тема не исчерпана, потому всё ещё хотелось бы услышать советы.

Есть новые подробности. Появилась мысль что возникают "зомби". Потому в родительском процессе дописал обработчик сигналов и на самом деле обнаружил, что в дочернем процессе при cd directory возникает зомби. Но, когда я после выполнения смены директории принудительно завершаю процесс, то chdir вообще не работает. Что не так? Жду любых идей, советов, предположений. Спасибо.

 
Код:
if(strcmp("cd", command) == 0)
   {
      int test = chdir(element);
      send(clntSocket, "Ok", 2, 0);
      close(clntSocket);
      exit(0);
   }
53K
11 февраля 2010 года
nicolaus2
13 / / 02.02.2010
Понимаю, что не очень хочется копаться в чужом коде, но, реально, ребята, я уже в отчаянии) И мне уже кажется что я всё в корне неправильно сделал. Объясню сложившуюся ситуацию. cat и ls вроде как работают. Проблема возникает при cd, при чём после вызова cd не возвращается сигнал о завершении и в обработчик сигналов ничего не приходит, дочерний процесс убивает родительский, резервной функцией killsomeone(). А вообще killsomeone() миролюбивая и при нормальных обстоятельствах она не вызывается, в том числе при ls и cat. После вызова cd, итеративно вызываю ls и выводится по очереди листинг то той директории что была, то той, на которую изменили. Буферы вроде чистятся, проблему никак не пойму. Спасибо всем, кто всё-таки осилит код.

Клиент:
Код:
#include <stdio.h>      
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>    
#include <string.h>    
#include <unistd.h>    

#define RCVBUFSIZE 255  

void DieWithError(char *errorMessage);  /* Error handling function */

int main(int argc, char *argv[])
{
   int sock;                    
   struct sockaddr_in echoServAddr;
   unsigned short echoServPort;    
   char *servIP;                    
   char *echoCommand;     // сюда запоминаем команду - ls,cd или cat          
   char echoBuffer[RCVBUFSIZE];    
   unsigned int echoCommandLen;      
   int bytesRcvd, totalBytesRcvd;  

   char *echoElement; // сюда директорию/файл
   unsigned int echoElementLen;


   if ((argc < 3) || (argc > 5))    /* Test for correct number of arguments */
   {
      fprintf(stderr, "Usage: %s <Server IP> [<Echo Port>] <Echo Command> \n",
      argv[0]);
      exit(1);
   }

   servIP = argv[1];            
   echoCommand = argv[3];    
   if(argc == 5)
      echoElement = argv[4];
   if ((argc == 4) || (argc == 5))
      echoServPort = atoi(argv[2]);
   else
      echoServPort = 7;

   if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
      DieWithError("socket() failed");


   memset(&echoServAddr, 0, sizeof(echoServAddr));    
   echoServAddr.sin_family      = AF_INET;            
   echoServAddr.sin_addr.s_addr = inet_addr(servIP);  
   echoServAddr.sin_port        = htons(echoServPort);

   if (connect(sock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0)
      DieWithError("connect() failed");

   echoCommandLen = strlen(echoCommand);        
   if(argc == 5)
      echoElementLen = strlen(echoElement);
   if (send(sock, echoCommand, echoCommandLen, 0) != echoCommandLen)
      DieWithError("send() sent a different number of bytes than expected");
   if(argc == 5) // если аргументов 5, тоесть это команды cd /, cat 1.txt,
 то посылаем и команду, и элемент для открытия/перехода
   {
      printf("%s\n", echoElement);
      if (send(sock, echoElement, echoElementLen, 0) != echoElementLen)
         DieWithError("send() sent a different number of bytes than expected");
   }
   else // если команда ls, то посылаем пустую строку, так как на сервере принимаем дважды и нужно что то послать
      send(sock, " ", 1, 0);

    printf("Received: \n");                /* Setup to print the echoed string */

      if ((bytesRcvd = recv(sock, echoBuffer, RCVBUFSIZE - 1, 0)) <= 0)
         DieWithError("recv() failed or connection closed prematurely");
      echoBuffer[bytesRcvd] = '\0';
      printf("%s", echoBuffer);    
      memset( echoBuffer, 0, RCVBUFSIZE); // очищаем все буферы
      fflush(stdout);
      fflush(stdin);
      fflush(stderr);

   printf("\n");    
   close(sock);
   exit(0);
}

Сервер:
Код:
#include <stdio.h>      
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>    
#include <string.h>    
#include <unistd.h>  
#include <signal.h>
#include <sys/wait.h>

#define MAXPENDING 5    /* Maximum outstanding connection requests */

void DieWithError(char *errorMessage);  /* Error handling function */
void HandleTCPClient(int clntSocket);   /* TCP client handling function */
void child_waiter(int);
void killsomeone(int);

int main(int argc, char *argv[])
{
   int servSock;                    /* Socket descriptor for server */
   int clntSock;                    /* Socket descriptor for client */
   struct sockaddr_in echoServAddr; /* Local address */
   struct sockaddr_in echoClntAddr; /* Client address */
   unsigned short echoServPort;     /* Server port */
   unsigned int clntLen;            /* Length of client address data structure */

   if (argc != 2)     /* Test for correct number of arguments */
   {
       fprintf(stderr, "Usage:  %s <Server Port>\n", argv[0]);
       exit(1);
   }

   echoServPort = atoi(argv[1]);  /* First arg:  local port */
   signal(SIGCHLD, child_waiter); // обработчик сигналов, для избежания зомби

    /* Create socket for incoming connections */
   if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
       DieWithError("socket() failed");

    /* Construct local address structure */
   memset(&echoServAddr, 0, sizeof(echoServAddr));   /* Zero out structure */
   echoServAddr.sin_family = AF_INET;                /* Internet address family */
   echoServAddr.sin_addr.s_addr = inet_addr("0.0.0.0"); /* Any incoming interface */
   echoServAddr.sin_port = htons(echoServPort);      /* Local port */

   if (bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0)
       DieWithError("bind() failed");
   if (listen(servSock, MAXPENDING) < 0)
       DieWithError("listen() failed");
   for (;;)
        clntLen = sizeof(echoClntAddr);
      if ((clntSock = accept(servSock, (struct sockaddr *) &echoClntAddr,
                               &clntLen)) < 0)
         DieWithError("accept() failed");

      int parent_pid = getpid();

      int child_pid;
      if(fork() == 0)
      {
         printf("--------------------------------------\n");
         child_pid = getpid();
         printf("Parent's ID - %d\n", parent_pid);
         printf("Yours child process ID - %d\n", child_pid);
         printf("Handling client %s\n", inet_ntoa(echoClntAddr.sin_addr));
         HandleTCPClient(clntSock);
         killsomeone(child_pid);
      }
    }
    /* NOT REACHED */
}

void child_waiter(int signum) // обработчик сигналов
{
      //wait(NULL);
   while(waitpid(-1, NULL, WNOHANG) > 0)
   printf("Child process died\n");
   printf("--------------------------------------\n");

}

void killsomeone(int pid) // резервная функция для убивания процесса, если он стал
сиротой в том случае, если оборвалась связь между родителем и дочерним,
он не отправил сигнал о своём завершении ... или других казусах,
 в любом случае дочерний процесс должен умереть.
{
   if(kill(SIGKILL, pid) == 0);
   printf("\nOh, I killed %d\n", pid);
   printf("--------------------------------------\n");
}

#include <stdio.h>      /* for printf() and fprintf() */
#include <sys/socket.h> /* for recv() and send() */
#include <unistd.h>   /* for close() */
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#define size 255  /* Size of receive buffer */

void DieWithError(char *errorMessage);  /* Error handling function */
void clear(char [], char [], char []);

void HandleTCPClient(int clntSocket)
{
    char echoBuffer[size];        /* Buffer for string */
    int recvMsgSize;                    /* Size of received message */
    char command[size]; // сюда записуем команду
    char element[size]; // сюда директорию или файл
   clear(echoBuffer, command, element); // функция очистки

    /* Receive message from client */
    if ((recvMsgSize = recv(clntSocket, echoBuffer, size, 0)) < 0) //первый раз принимаем
                                                                                                команду
        DieWithError("recv() failed");

   if(strcmp("cat", echoBuffer) == 0)
      memcpy(command, echoBuffer, recvMsgSize);
   if(strcmp("ls", echoBuffer) == 0)
      memcpy(command, echoBuffer, recvMsgSize);
   if(strcmp("cd", echoBuffer) == 0)
      memcpy(command, echoBuffer, recvMsgSize);

  if ((recvMsgSize = recv(clntSocket, echoBuffer, size, 0)) < 0) //второй раз записываем файл/директорию
        DieWithError("recv() failed");
     memcpy(element, echoBuffer, recvMsgSize);

   printf("We recieve from client:\n");
   printf("%s\n", command);
   printf("%s\n", element);

   if(strcmp("ls", command) == 0) //выполняем лс
   {
      clear(echoBuffer, command, element);
      dup2(clntSocket, 1);
      dup2(clntSocket, 2);
      close(clntSocket);
      execlp("/bin/ls","ls",NULL);
   }
   if(strcmp("cat", command) == 0) //кат
   {
      dup2(clntSocket, 1);
      dup2(clntSocket, 2);
      close(clntSocket);
      execlp("/bin/cat", "cat", element, NULL);
      clear(echoBuffer, command, element);
   }
   if(strcmp("cd", command) == 0)// цд
   {
      int test = chdir(element);
      send(clntSocket, "Ok", 2, 0);
      clear(echoBuffer, command, element);
      close(clntSocket);
   }
   else
   {
      send( clntSocket, "You input smth wrong\n", size, 0);
      clear(echoBuffer, command, element);
      close(clntSocket);
   }
}

void clear(char a[], char b[], char c[])
{
   memset(a, 0, size);
   memset(b, 0, size);
   memset(c, 0, size);
   fflush(stdout);
   fflush(stdin);
   fflush(stderr);
   printf("All arrays cleared\n");
}

Функция завершения с ошибкой:
 
Код:
#include <stdio.h>  /* for perror() */
#include <stdlib.h> /* for exit() */

void DieWithError(char *errorMessage)
{
    perror(errorMessage);
    exit(1);
}
43K
13 февраля 2010 года
loki231
76 / / 27.09.2009
Да уж, копаться в таком коде - удовольствие то ещё.....

Во-первых, уберите функцию killsomeone() и напишите вместо неё exit(0). Надеюсь, это станет понятно после man exit. А можно и ещё проще - return 0;

Во-вторых, системный вызов chdir() меняет рабочую директорию того процесса, который её вызвал. У Вас же это не серверный процесс, который принимает соединения, а порождённый fork()'ом процесс-детишка. В нём то и меняется рабочая директория. А в родительском процессе она остаётся без изменения. Поэтому обработка команды "cd" должна происходить в родительском процессе. Тогда все последующие процессы-детишки унаследуют новую рабочую директорию. Это два.

Три. Странный способ работы с сокетом. Первое чтение какое-то, второе. Вы должны рассматривать TCP'шный сокет как трубу, из которой сплошным потоком, без какого-то разделения, обусловленного последовательностью записи, идут данные. Например, первый read() в сервере может легко вычитать из сокета не только код команды, но и её аргумент. Как девки спляшут. Так делать нельзя. Можно сделать, например, так. Каждое сообщение серверу должно заканчаваться символом перевода строки( или нулём или чем-хотите). Сначала send(cmd), потом если есть аргумент send(" ", 1); send (arg, strlen(arg)); send("\n", 1); Ну или всё в одном буфере скомпоновать и за раз всё отправить.
 
Код:
strcpy (buf, cmd);
     if (arg) {
          strcat (buf, " ");
          strcat (buf, arg);
     }
     strcat (buf, "\n");
     send (buf, strlen(buf));

И разбор принятого от клиента сообщения тоже изменится. Прочитал порцию данных. Поискал там '\n'. Если нет, читаем дальше. Если есть, то от начала буфера и до '\n' это и есть сообщение клиента. Ищем в нем первый пробел. Если есть, то нашли команду и её аргумент. Если нет, то это просто команда, без аргумента. Всё просто.

В-четвёртых. Что будет, если ответ сервера (например результат ls или cat) будет больше чем 255 байт... И что с этим надо делать...
53K
13 февраля 2010 года
nicolaus2
13 / / 02.02.2010
Спасибо большое, loki231, Вы докончили моё понимание правильной работы с сокетами. Дело в том что гуглевскими методами сформировалось немного(а может и много) неправильная идеология.

Только немного не так, как Вы сказали. Да, chdir() должен вызываться в дочернем процессе. А весь процесс работы с сокетами организовать по принципу telnet-а. Клиент подключается, для обработки всех его запросов порождается дочерний процесс. Клиент делает что ему надо, посылает exit, q либо quit, дочерний процесс завершает свою работу, как и клиент.

С этим всем мне стоит разобраться на выходных.

Ещё раз спасибо, loki231. Вы поставили блуждающего человека на путь истины)
43K
14 февраля 2010 года
loki231
76 / / 27.09.2009
Я тоже думаю, что для каждого клиента должен порождаться свой отдельный процесс, который и будет выполнять команды cd, ls и cat. а то ведь может получиться, что один клиент сменит директорию, а другой и не заметит этого. Будет только удивляться, что это ls у него то одну директорию показывает, то другую. Короче для каждого подключившегося клиента - свой процесс и отдельная сессия. Та реализация протокола, что была приведена в качестве примера ренне (с '\n' в конце команды) как раз подходит для этого.

Ещё немного, и Вы напишете свой telnetd и telnet... Кстати, в качестве клиента, прямо сейчас, можете смело использовать стандартный telnet.
53K
14 февраля 2010 года
nicolaus2
13 / / 02.02.2010
//удалить
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог