1

我想为 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;
}
4

2 回答 2

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;
}

代码需要重构为函数。它太长了。我试图做尽可能少的改变,所以我把重组留作练习。

于 2013-02-20T12:16:01.590 回答
0
    fscanf(stdin,"%79s",s);

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

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

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

于 2013-02-20T02:47:22.863 回答