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);
}
exec и перенаправление в буфер/сокет + exec cd
1. Есть сервер и клиент на основе TCP/IP сокетов. Сервер может обрабатывать запросы cat, ls, cd. Команды на сервере запускаются посредством функций семейства exec. В этом проблема и состоит: есть два терминала, в одном - сервер, во втором клиент делает запрос, скажем на ls, информация должна отразиться в терминале клиента.
Как перенаправить вывод на терминал именно клиента? Так как по умолчанию exec вывод происходит на stdout, которым является терминал сервера. Или хотя бы напрямую в массив, а не через файл?
Манипулирование с помощью dup2() результатов не принесло(возможно что то не так делал).
2. Мы выполняем команды exec путём вызова исполняемых файлов из /bin, но там нету файла <cd> (для открытия директорий). Есть ли аналог для отрытия директорий при выполнении exec, если да, то какой.
Буду благодарен за подсказки и советы. Спасибо.
Многое в постановке задаче осталось за кадром, но попробуем дедуктивным методом составить общую картину.
Раз были упомянуты сокеты и TCP/IP, то клиент и сервер работают на разных машинах, сколько каналов и как они организованы остаётся неизвестным. Предположим что взаимодействие производится по одному каналу.
Тогда так.
Код:
cd стоит особняком, это не внешняя программа, а встроенная команда sh, выполняя которую, sh вызывает функцию chdir (const char *). Придётся серверу разбирать команды запросов и если это "cd some/dir/here", то выполнять самому chdir ("some/dir/here").
С чдиром пока не разбирался.
Спасибо, loki231.
Тогда ещё один вопрос: как считаете, команду и директорию/файл для открытия стоит передавать по отдельности send-ом или одной кашей "команда_директория/файл", а на сервере уже разбирать где какая команда, а где директория/файл?
Передавайте в одной команде, конечно. Зачем Вам париться с повторным приёмом сообщения? Если сообщение не дойдёт, то пусть уж полностью :)
Когда делаю chdir(), а потом ls, то происходит следующее: по умолчанию, на сервера, стоит та директория где и находится сам код сервера и откуда я его запускаю, при смене директории и последующих ls, сервер возвращает то список файлов старой директории, то список файлов новой, изменённой директории, чередуя их. Если изменю директорию два раза, то это "чередование" будет состоять из списка файлов из трёх директорий.
На всякий случай код cd:
Код:
if(strcmp("cd", command) == 0)
{
int test = chdir(element);
send(clntSocket, "Ok", 3, 0);
}
{
int test = chdir(element);
send(clntSocket, "Ok", 3, 0);
}
В command-е сохраняется сама команда, возможные варианты - cat, ls, cd. В element сохраняется директория для изменения.
Подскажите, пожалуйста, в чём может быть дело.
Я бы предположил, что не чистится приёмный буфер. Команды накапливаются в нём и каждый раз, при поступлении новой команды выполняется некоторая последовательность команд, полученных ранее. Аналогично с буфером передачи.
Цитата: loki231
Я бы предположил, что не чистится приёмный буфер. Команды накапливаются в нём и каждый раз, при поступлении новой команды выполняется некоторая последовательность команд, полученных ранее. Аналогично с буфером передачи.
Хорошая версия, спасибо, но команды ls и cat без cd нормально работают. И, если бы буфер не чистился, то глюки были бы со всеми командами, а не только с ls при cd. Да и нету там, в буфере, команды смены директории на директорию что была по умолчанию. Тоесть, если я меняю chdir("/"), то в буфере нету команды, чтобы изменить директорию обратно, на ту что была с самого начала.
Есть новые подробности. Появилась мысль что возникают "зомби". Потому в родительском процессе дописал обработчик сигналов и на самом деле обнаружил, что в дочернем процессе при cd directory возникает зомби. Но, когда я после выполнения смены директории принудительно завершаю процесс, то chdir вообще не работает. Что не так? Жду любых идей, советов, предположений. Спасибо.
Код:
if(strcmp("cd", command) == 0)
{
int test = chdir(element);
send(clntSocket, "Ok", 2, 0);
close(clntSocket);
exit(0);
}
{
int test = chdir(element);
send(clntSocket, "Ok", 2, 0);
close(clntSocket);
exit(0);
}
Клиент:
Код:
#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 <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 <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);
}
#include <stdlib.h> /* for exit() */
void DieWithError(char *errorMessage)
{
perror(errorMessage);
exit(1);
}
Во-первых, уберите функцию 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));
if (arg) {
strcat (buf, " ");
strcat (buf, arg);
}
strcat (buf, "\n");
send (buf, strlen(buf));
И разбор принятого от клиента сообщения тоже изменится. Прочитал порцию данных. Поискал там '\n'. Если нет, читаем дальше. Если есть, то от начала буфера и до '\n' это и есть сообщение клиента. Ищем в нем первый пробел. Если есть, то нашли команду и её аргумент. Если нет, то это просто команда, без аргумента. Всё просто.
В-четвёртых. Что будет, если ответ сервера (например результат ls или cat) будет больше чем 255 байт... И что с этим надо делать...
Только немного не так, как Вы сказали. Да, chdir() должен вызываться в дочернем процессе. А весь процесс работы с сокетами организовать по принципу telnet-а. Клиент подключается, для обработки всех его запросов порождается дочерний процесс. Клиент делает что ему надо, посылает exit, q либо quit, дочерний процесс завершает свою работу, как и клиент.
С этим всем мне стоит разобраться на выходных.
Ещё раз спасибо, loki231. Вы поставили блуждающего человека на путь истины)
Ещё немного, и Вы напишете свой telnetd и telnet... Кстати, в качестве клиента, прямо сейчас, можете смело использовать стандартный telnet.
//удалить