1

UNIX用の簡単なチャットアプリケーションを作りたいです。複数のクライアントをサポートする 1 つのサーバーを作成しました。新しいクライアントがサーバーに接続するたびに、fork コマンドを使用して新しいプロセスが作成されます。問題は、すべての子プロセスがサーバー上で同じ stdin を共有していることです。このため、2 番目のクライアントにメッセージを送信するには、1 番目の子プロセスを終了する必要があります。これを解決するために、各子プロセスを新しいターミナルで実行したいと思います。これは、子プロセス コードのコードを新しいファイルに記述し、 xterm -e sh -c のように実行することで実現できます (ただし、これは試していません)。

私が本当に望んでいるのは、新しい端末を起動して残りのコードを実行するためだけに2つのファイルを持たないことです。

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

クライアントとサーバーの間で送信されるメッセージは少し変わっていると思います。この単純な「動作をテストするだけ」のシナリオでは、クライアントがサーバーにメッセージを送信するのがより一般的です。例として、クライアントが送信するすべてのものをクライアントにミラーリングする単純なエコー サービスを挙げることができます。この設計は、何らかの要件によって強制されますか?

批判はさておき、現在のデザインを機能させる 2 つの個別の変更があります。どちらも、サブサーバーでの入力の読み取りを変更する必要があります。

代替案 1: stdin から読み取る代わりに、名前付きパイプ ( を参照man 3 mkfifo)、fex /tmp/childpipe"pid_of_subserver_here" を作成します。パイプを作成しsay()て、読み取り用に開くことができます。次に、echo ( man echo) を使用して、パイプ echo "My message" > /tmp/childpipe"NNNN" に書き込みます。子を終了する前に、パイプを削除することを忘れないでくださいunlink()

代替案 2: サーバーと各サブサーバーの間に名前のないパイプを作成します。これにより、コードがかなり厄介になりますが、名前付きパイプの作成とエコーの使用が回避されます。サンプルコードを以下に示します。エラー処理が不十分で (ほとんどのサンプル コードと同様)、クライアントの切断を適切に処理しません。

使用例: 1) サーバー ./a.out を起動します 2) (外部ウィンドウでクライアントに接続します (例: nc localhost 30000) 3) 「1Hello client one」と入力してクライアント 1 に書き込みます 4) (3 番目のウィンドウで 2 番目のクライアントを接続するなど) 4) 「2Hello second client」と入力して、2 番目のクライアントに書き込みます。

#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-chatですか?クライアントごとにいくつかのソケットがあり、何かを「言いたい」場合は、クライアントを使用する必要があります。それは本当の論理です。

サーバーは通常、サービスメッセージのみを送信します。それも本当の論理です。

ただし、新しい端末が必要な場合は、unistd.hのexecのファミリを使用してみてください。

于 2013-02-20T02:47:22.863 に答える