Выполнение дочернего процесса в новом терминале
Я хочу сделать простое приложение для чата для Unix. Я создал один сервер, который поддерживает несколько клиентов. Когда новый клиент подключается к серверу, новый процесс создается с помощью команды fork. Теперь проблема заключается в том, что все дочерние процессы совместно используют один и тот же stdin на сервере, поэтому для того, чтобы отправить сообщение второму клиенту, 1-й дочерний процесс должен завершиться. Для решения этой проблемы я хотел бы запустить каждый дочерний процесс в новом терминале. Этого можно достичь, написав код для кода дочернего процесса в новом файле и выполнив его как xterm -e sh -c (хотя я не пробовал этого).
Что я действительно хочу, так это не иметь два файла просто для запуска нового терминала и запуска остальной части кода в нем.
int say(int socket)
{
char *s;
fscanf(stdin,"%79s",s);
int result=send(socket,s,strlen(s),0);
return result;
}
int main()
{
int listener_d;
struct sockaddr_in name;
listener_d=socket(PF_INET,SOCK_STREAM,0);
name.sin_family=PF_INET;
name.sin_port=(in_port_t)htons(30000);
name.sin_addr.s_addr=htonl(INADDR_ANY);
int c = bind(listener_d,(struct sockaddr *)&name,sizeof(name)); //Bind
if(c== -1)
{
printf("\nCan't bind to socket\n");
}
if(listen(listener_d,10) == -1) // Listen
{
printf("\nCan't listen\n");
}
puts("\nWait for connection\n");
while(1)
{
struct sockaddr_storage client_addr;
unsigned int address_size = sizeof(client_addr);
int connect_d = accept(listener_d,
(struct sockaddr*)&client_addr,&address_size); //Accept
if(connect_d== -1)
{
printf("\nCan't open secondary socket\n");
}
if(!fork())
{
close(listener_d);
char *msg = "welcome Sweetone\n";
if(send(connect_d,msg,strlen(msg),0))
{
printf("send");
}
int k=0;
while(k<5)
{
say(connect_d);
++k;
}
close(connect_d);
exit(0);
}
close(connect_d);
}
close(listener_d);
return 0;
}
2 ответа
Я думаю, что отправка сообщений между вашим клиентом и серверами немного необычна. В этом простом сценарии "просто проверь, как это работает" клиенты чаще отправляют сообщения на сервер. В качестве примера я мог бы упомянуть простой эхо-сервис, который отражает все, что клиент отправляет обратно клиенту. Этот дизайн обусловлен некоторыми требованиями?
Помимо критических замечаний, у меня есть два отдельных изменения, которые могут сделать ваш текущий дизайн работ. Они оба включают изменение чтения входных данных на субсерверах.
Альтернатива 1: вместо чтения из стандартного ввода создайте именованный канал (см. man 3 mkfifo
), fex /tmp/childpipe"pid_of_subserver_here". Вы могли бы создать трубу в say()
и откройте его для чтения. Тогда используйте эхо (man echo
) записать в трубу эхо "Моего сообщения" > /tmp/childpipe"NNNN". Прежде чем покинуть ребенка, не забудьте удалить трубу с unlink()
Альтернатива 2: Создать неназванный канал между сервером и каждым субсервером. Это делает код намного более запутанным, но избегает создания именованных каналов и использования echo. Пример кода приведен ниже. Он имеет недостаточную обработку ошибок (как большинство примеров кода) и не обрабатывает отключение клиента должным образом.
Пример использования: 1) запустить сервер./a.out 2) (подключить клиента во внешнем окне (например, nc localhost 30000) 3) написать клиенту 1, набрав "1Hello client one" 4) (подключить второго клиента в третьем окне и т. Д.) 4) Напишите второму клиенту, набрав "2Hello second client"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
enum max_childeren{
MAX_CHILDEREN = 50
};
int say(int socket)
{
char buf[513] = {0};
fgets(buf, sizeof(buf), stdin);
int result=send(socket, buf, strlen(buf),0);
return result;
}
int main()
{
int listener_d;
struct sockaddr_in name;
listener_d=socket(PF_INET,SOCK_STREAM,0);
name.sin_family=PF_INET;
name.sin_port=(in_port_t)htons(30000);
name.sin_addr.s_addr=htonl(INADDR_ANY);
int on = 1;
if (setsockopt(listener_d, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0){
perror("setsockopt()");
}
int c = bind(listener_d,(struct sockaddr *)&name,sizeof(name)); //Bind
if(c== -1)
{
printf("\nCan't bind to socket\n");
}
if(listen(listener_d,10) == -1) // Listen
{
printf("\nCan't listen\n");
}
// Edited here
int number_of_childeren = 0;
int pipes[2] = {0};
int child_pipe_write_ends[MAX_CHILDEREN] = {0};
fd_set select_fds;
FD_ZERO(&select_fds);
puts("\nWait for connection\n");
while(1)
{
struct sockaddr_storage client_addr;
unsigned int address_size = sizeof(client_addr);
// Edited here, to multiplex IO
FD_SET(listener_d, &select_fds);
FD_SET(STDIN_FILENO, &select_fds);
int maxfd = listener_d + 1;
int create_new_child = 0;
int connect_d = -1; // moved here
select(maxfd, &select_fds, NULL, NULL, NULL);
if (FD_ISSET(listener_d, &select_fds)){
connect_d = accept(listener_d,
(struct sockaddr*)&client_addr,&address_size); //Accept
if(connect_d== -1)
{
printf("\nCan't open secondary socket\n");
exit(EXIT_FAILURE);
}
create_new_child = 1;
}
char buf[512] ={0};
char *endptr = NULL;
if (FD_ISSET(STDIN_FILENO, &select_fds)){
fgets(buf, sizeof(buf), stdin);
long int child_num = strtol(buf, &endptr, 10);
if (child_num > 0 && child_num <= number_of_childeren) {
write(child_pipe_write_ends[child_num - 1], endptr, strnlen(buf, sizeof(buf)) - (endptr - buf));
}
else {
printf("Skipping invalid input: %s\n", buf);
}
}
if (create_new_child != 1)
continue;
number_of_childeren++; // Edited here
int error = pipe(pipes);
if (error != 0){
//handle errors
perror("pipe():");
exit(EXIT_FAILURE);
}
child_pipe_write_ends[number_of_childeren - 1] = pipes[1];
if(!fork())
{
error = dup2(pipes[0], STDIN_FILENO);
if (error < 0){ // could also test != STDIN_FILENO but thats confusing
//handle errors
perror("dup2");
exit(EXIT_FAILURE);
}
close(pipes[0]);
close(listener_d);
char *msg = "welcome Sweetone\n";
if(send(connect_d,msg,strlen(msg),0))
{
printf("send\n");
}
int k=0;
while(k<5)
{
say(connect_d);
++k;
}
close(connect_d);
exit(0);
}
close(connect_d);
close(pipes[0]);
}
close(listener_d);
return 0;
}
Код нуждается в рефакторинге в функции. Это слишком долго. Я попытался внести как можно меньше изменений, поэтому я оставил реструктуризацию в качестве упражнения.
fscanf(stdin,"%79s",s);
Зачем? Это tcp-чат? У вас есть сокет для каждого клиента, и если вы хотите что-то сказать, вы должны использовать клиент. Это настоящая логика.
Сервер обычно отправляет только служебные сообщения. Это тоже логика.
Но если вам нужен новый терминал, то вы можете попробовать использовать семью исполнителя из unistd.h.