【问题标题】:Executing child process in new terminal在新终端中执行子进程
【发布时间】:2013-01-31 06:42:16
【问题描述】:

我想为 unix 制作一个简单的聊天应用程序。 我创建了一台支持多个客户端的服务器。每当有新客户端连接到服务器时,就会使用 fork 命令创建一个新进程。现在的问题是所有子进程在服务器上共享相同的标准输入,因此为了向第二个客户发送消息,第一个子进程必须终止。为了解决这个问题,我想在新终端中运行每个子进程。 这可以通过在新文件中编写子进程代码的代码并像 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;
}

【问题讨论】:

  • stdin 读取时,是否应该将该输入发送到所有连接的客户端?您是否考虑过线程而不是进程?
  • 是的,这是一个示例程序。我肯定会转向线程。就从标准输入的读取而言,会发生什么情况是标准输入的数据进入第一个连接一次的客户端,只有在你输入任何内容时才会终止,然后它才会进入嵌套客户端。

标签: c sockets unix multiprocessing unix-socket


【解决方案1】:

我认为您的客户端和服务器之间发送的消息有点不寻常。更常见的是,在这个简单的“只是测试它是如何工作的”场景中,让客户端向服务器发送消息。作为一个例子,我可以提到一个简单的回显服务,它将客户端发送的所有内容镜像回客户端。这种设计是出于某些要求吗?

撇开批评不谈,我有两个单独的更改可以使您当前的设计工作。它们都涉及更改子服务器中输入的读取。

备选方案 1: 不要从标准输入读取,而是创建一个命名管道(参见man 3 mkfifo),fex /tmp/childpipe"pid_of_subserver_here"。您可以在say() 中创建管道并打开它以供阅读。然后使用 echo (man echo) 写入管道 echo "My message" > /tmp/childpipe"NNNN"。在退出孩子之前,记得用unlink()移除管道

备选方案 2: 在服务器和每个子服务器之间创建一个未命名的管道。这使代码更加混乱,但避免了创建命名管道和使用 echo。下面包含示例代码。它没有足够的错误处理(像大多数示例代码一样)并且不能正确处理断开客户端的连接。

示例用法:1)启动服务器./a.out 2)(在外部窗口连接客户端(例如nc localhost 30000)3)通过键入“1Hello client one”写入客户端1 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;
}

代码需要重构为函数。它太长了。我试图做尽可能少的更改,因此我将重组留作练习。

【讨论】:

  • 感谢您的精彩回复。一旦我分析和理解它,肯定会从代码中学到很多东西。我刚刚阅读了一些关于套接字编程的内容,旨在创建我自己的基于终端的 p2p 聊天应用程序(Havent 研究了如何制作 std p2p 聊天应用程序。)。这就是为什么我想在客户端连接时打开一个新终端,这样我将为每个客户端提供不同的窗口,我可以通过这些窗口与他们聊天。让我知道我还能做些什么来进一步朝着预期的方向发展。谢谢。
  • 作为在线资源,许多人喜欢Beejs guideUNIX Network Programming 是一本书,它会告诉你几乎所有你需要的东西,然后是一些。 APUE 和它的现代 Linux 仅等效 Linux Programming Interface 是一个经典,尽管不仅仅是关于网络编程。首先,我认为 Beejs 指南和阅读任何不熟悉的库和系统调用的手册页会很好。
【解决方案2】:
    fscanf(stdin,"%79s",s);

为什么?是tcp聊天吗?每个客户端都有一些套接字,如果你想“说”一些东西,那么你必须使用客户端。很合乎逻辑。

服务器通常只发送服务消息。这也是真正的逻辑。

但是如果你想要新的终端,那么你可以尝试使用 unistd.h 中的 exec 家族。

【讨论】:

  • 嗨,我替换了 if(!fork()) 块中的代码并将其放置在一个新文件中(也可以说函数)。当我使用 system(./subserver listener_d connection_d) 时,子程序并没有真正按预期工作。客户端没有收到我从服务器发送的任何消息。
  • 如果我将 close(listener_d) 留在原处,并将其余代码放在 if(!fork()) 块中的其他文件中,则服务器会发疯。我猜它分叉了许多进程,因为在那之后我无法使用我的机器,因为它太忙了。
  • 我试过类似 system("xterm -e .../subServer arg1 arg2"); xterm 成为我的主服务器的子进程,而不是我的子服务器。而且功能不符合预期。
  • 尝试调用 execl("/bin/xterm","xterm","-e","%path_to_subprogram%","%socket_descriptor%") 或类似的东西,而不是 "while( k
  • execl("/usr/bin/xterm","xterm","-e","%path_to_subprogram%","%socket_descriptor%",NULL) 当然。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-06
  • 1970-01-01
  • 2018-09-14
  • 1970-01-01
  • 1970-01-01
  • 2013-11-08
相关资源
最近更新 更多