質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.35%

複数クライアント接続対応のサーバプログラムの状態遷移

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 967

score 8

複数クライアント接続

問題

 ユーザー名の登録などがうまく行かないです。空白文字で区切ったりしてはいるのですが。
また、クライアントがEOFを入力したら、そのユーザーのみのソケットを閉じて入力、受付待ちに戻るはずなのですが、ユーザー名を大量に出力して作業が強制的に終了してしまいます。

schat.c(サーバプログラム)

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 256
#define TIMEOUT 10
#define MAXCLIENTS 5
void myalarm(int sec);
void timeout();
int main(int argc,char **argv)
{
    int sock,h=0,k=5,csock[5];/*クライアントを受け付けたソケット*/
    struct sockaddr_in clt;
    struct sockaddr_in svr;
    struct hostent *cp;
    int clen,nbytes,fdmax,flag;
    int Nbyte[5];
    char  USERNAME[5][32],Nbuf[32];
    fd_set rfds;/* select()で用いるファイル記述子集合*/
    struct timeval tv; 
    char rbuf[128],buffer[128],*arg;
    memset(rbuf, 0, sizeof(rbuf));
    for ( int i = 0; i <k; i++ ){
        csock[i] = -1;//通信用の配列の初期化
    }
    /* select()が返ってくるまでの待ち時間を指定する変数*/

    if ((sock=socket(AF_INET,SOCK_STREAM,0))<0) {
        perror("socket");
        exit(1);
    }
    /*クライアントからの接続待ち受けなど*/
    bzero(&svr,sizeof(svr));
    svr.sin_family=AF_INET;
    svr.sin_addr.s_addr=htonl(INADDR_ANY);
    svr.sin_port=htons(10140);
    if(bind(sock,(struct sockaddr *)&svr,sizeof(svr))<0) {   
        perror("bind");
        exit(1);  
    }
    if (listen(sock,5)<0) {
      /*待ち受け数に5を指定*/
        perror("listen");
        exit(1);
    }
    while(1){
        FD_ZERO(&rfds);
        FD_SET(sock,&rfds);
        fdmax=sock;
        for(int i=0;i<MAXCLIENTS;i++){
            if(csock[i]>0){
                if(csock[i]>fdmax){
                    fdmax=csock[i];
                }
                FD_SET(csock[i],&rfds);
            }
        }
        tv.tv_sec = 1;
        tv.tv_usec = 0;/*標準入力とソケットからの受信を同時に監視する*/
        section3:
        if(select(fdmax+1,&rfds,NULL,NULL,&tv)>0){
            if(FD_ISSET(sock,&rfds)){
                for(int i=0;i<MAXCLIENTS;i++){
                    clen=sizeof(clt);
                    if ( ( csock[i] = accept(sock,(struct sockaddr *)&clt,&clen) ) <0 ) {
                        perror("accept");
                        exit(2);
                    }
                    if(sock<MAXCLIENTS){
                        write(csock[i],"REQUEST ACCEPTED\n",17);
                        nbytes=read(csock[i],rbuf,sizeof(rbuf));
                        strncpy(Nbuf,rbuf,nbytes-1);
                        Nbyte[i]=nbytes;
                        flag=0;
                        for(int j=0;j<MAXCLIENTS;j++){
                            if(strcat(USERNAME[j],Nbuf)==0){
                                flag=1;
                                char name[32]="REJECTED";
                                write(csock[i],"USERNAME REJECTED\n",18);
                                strcat(name,Nbuf);
                                for(int k=0;k<MAXCLIENTS;k++)
                                    write(csock[k],name,sizeof(name));
                                close(csock[i]);
                                goto section3;
                            }
                        }
                        if(flag==0){
                            write(csock[i],"USERNAME REGISTERED\n",20);
                            strncpy(USERNAME[i],Nbuf,Nbyte[i]-1);
                            char nll[]="\0";
                            strcat(USERNAME[i],nll);
                            write(1,USERNAME[i],sizeof(USERNAME[i]));
                            k++;
                            goto section3;
                        }


                    }
                }
            }
            for(int i=0;i<MAXCLIENTS;i++){
                if(FD_ISSET(csock[i],&rfds)){
                    if(read(csock[i],rbuf,sizeof(rbuf))!=0){
                        char STRING[64]="<";
                        strcat(STRING,USERNAME[i]);
                        strcat(STRING,"\0");
                        strcat(STRING,">");
                        strcat(STRING,rbuf);
                        for(int j=0;j<MAXCLIENTS;j++){
                            write(csock[j],STRING,sizeof(STRING));
                        }
                    }else{
                        fprintf(stderr,"01\n");
                        close(csock[i]);
                        for(int j=0;MAXCLIENTS;j++){
                            write(csock[j],USERNAME[i],sizeof(USERNAME[i]));
                        }
                        USERNAME[i][0]='\0';
                        k--;

                    }
                } 
            }
        }
    }
    close(sock);
    exit(0);
} 
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • jimbe

    2019/08/07 23:09

    状態遷移が決まっているのでしたら, それに合わせて関数化したほうが見通しが良くなります.
    見通しが良くなると, それだけでバグが減る可能性が減ると期待できます.

    キャンセル

  • tatsu99

    2019/08/08 14:17

    main関数のなかでいろいろやりすぎです。
    いくつかの関数にわけて処理するようにしてはいかがでしょうか。
    クライアントから接続待ちとクライアントからの電文の受信待ちとを同じ個所で待っていますが、
    これは必ずそうしなさいということなのでしょうか。
    私としては
    ①クライアントからの接続待ち及びその対応処理
    ②クライアントからの電文の受信待ち及びその対応処理
    に分け、上記の①②をずっと繰り返すほうが、処理が簡単になるような気がします。

    キャンセル

  • tosakasimon

    2019/08/08 16:07

    ①②は同じ箇所で行う仕様です。

    キャンセル

回答 2

checkベストアンサー

+1

以下、サンプルコードです。mainが長いので機能単位に分けました。
USERNAMEは
USERNAME[MAXCLIENTS][USERNAMELEN+1];のように領域をとっています。ユーザ名の最大長は31文字にしています。
仕様上のkに該当するものはsock_countです。
sock_countがMAXCLIENTS未満ならユーザ名受信に移行しますが、そうでなければ、acceptしたソケットを閉じます。
sock_countは接続OK時に1カウントアップします。(ユーザー登録時ではありません)
ユーザー登録でリジェクト、もしくは、受信時にEOF検知の場合、sock_countから1減算します。
仕様上はユーザー登録時1カウントアップとなっていますが、これだと、ソケットがMAXCLIENTS分では足りなくなります。(接続だけ行うクライアントがいるとその数分ソケットが必要なため)
よって、sock_count(仕様上のk)のカウントアップは、接続OK時に行います。

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 256
#define TIMEOUT 10
#define MAXCLIENTS 5
#define USERNAMELEN 31
void myalarm(int sec);
void timeout();
//内部関数
void wait_receive();
void check_max_client(int sd);
int get_empty_ix(void);
void receive_msg(int ix);
void receive_user(int ix,char rbuf[]);
int check_user(char rbuf[]);
void receive_text(int ix,char rbuf[]);
//共通変数
int     sock;
int     csock[MAXCLIENTS];
int     csock_sts[MAXCLIENTS];  //0:ユーザー名未登録 1:ユーザ名登録済み
int     sock_count = 0;
char    USERNAME[MAXCLIENTS][USERNAMELEN+1];
int main(int argc,char **argv)
{
    int i;
    struct sockaddr_in svr;
    fd_set rfds;/* select()で用いるファイル記述子集合*/
    for ( int i = 0; i <MAXCLIENTS; i++ ){
        csock[i] = -1;//通信用の配列の初期化
        csock_sts[i] = 0;
    }
    /* select()が返ってくるまでの待ち時間を指定する変数*/

    if ((sock=socket(AF_INET,SOCK_STREAM,0))<0) {
        perror("socket");
        exit(1);
    }
    /*クライアントからの接続待ち受けなど*/
    bzero(&svr,sizeof(svr));
    svr.sin_family=AF_INET;
    svr.sin_addr.s_addr=htonl(INADDR_ANY);
    svr.sin_port=htons(10140);
    if(bind(sock,(struct sockaddr *)&svr,sizeof(svr))<0) {
        perror("bind");
        exit(1);
    }
    if (listen(sock,5)<0) {
      /*待ち受け数に5を指定*/
        perror("listen");
        exit(1);
    }
    while(1){
        //クライアントからの接続及び電文受信の待ち受け
        wait_receive();
    }
    close(sock);
    exit(0);
}
//クライアントからの接続及び電文受信の待ち受け
void wait_receive()
{
    struct sockaddr_in clt;
    struct timeval tv;
    fd_set  rfds;/* select()で用いるファイル記述子集合*/
    int     fdmax;
    int     ret;
    int     sd;
    int     clen;
    int     i;
    FD_ZERO(&rfds);
    FD_SET(sock,&rfds);
    fdmax=sock;
    for(int i=0;i<MAXCLIENTS;i++){
        if(csock[i]>=0){
            if(csock[i]>fdmax){
                fdmax=csock[i];
            }
            FD_SET(csock[i],&rfds);
        }
    }
    tv.tv_sec = 1;
    tv.tv_usec = 0;/*標準入力とソケットからの受信を同時に監視する*/
    ret = select(fdmax+1,&rfds,NULL,NULL,&tv);
    //エラーの場合
    if (ret < 0){
        perror("select");
        exit(2);
    }
    //タイムアウト
    if (ret == 0) return;
    //接続要求の有無
    if(FD_ISSET(sock,&rfds)){
        clen=sizeof(clt);
        sd = accept(sock,(struct sockaddr *)&clt,&clen);
        if (sd < 0){
            perror("accept");
            exit(2);
        }
        //最大クライアント数のチェック及びソケットの登録
        check_max_client(sd);
    }
    //電文受信の有無
    for (i = 0; i < MAXCLIENTS;i++){
        if (csock[i] >=0){
            if(FD_ISSET(csock[i],&rfds)){
                //電文受信処理
                receive_msg(i);
            }
        }
    }

}
//最大クライアント数のチェック及びソケットの登録
void check_max_client(int sd)
{
    int     i;
    char    req_acc[] = "REQUEST ACCEPTED\n";
    char    req_rej[] = "REQUEST REJECTED\n";
    if (sock_count < MAXCLIENTS){
        //最大クライアント数に達していないならクライアント用ソケットに登録する
        i = get_empty_ix();
        csock[i] = sd;
        csock_sts[i] = 0;
        write(sd,req_acc,strlen(req_acc));
        sock_count++;
        printf("connection completed sock_count=%d\n",sock_count);
    }else{
        //最大クライアント数に達しているなら、エラー応答し、ソケットをクローズする。
        write(sd,req_rej,strlen(req_rej));
        close(sd);
        printf("connection rejected sock_count overflow\n");
    }
}
//csockの中で空いているソケットの配列の添え字を返す
int get_empty_ix(void)
{
    int i;
    for (i = 0; i < MAXCLIENTS;i++){
        if (csock[i] == -1) return i;
    }
    //バグ以外あり得ないのでアボートする
    fprintf(stderr,"get_empty_ix error\n");
    exit(2);
}
//受信電文処理
void receive_msg(int ix)
{
    int nbytes;
    char rbuf[128];
    int len;
    memset(rbuf, 0, sizeof(rbuf));
    nbytes=read(csock[ix],rbuf,sizeof(rbuf)-1);
    if (nbytes <= 0){
        //受信エラー又はクライアントのソケットクローズの場合
        if (csock_sts[ix] == 1){
            //ユーザー名受信済みなら、ユーザー名を出力
            printf("closed user=<%s>\n",USERNAME[ix]);
        }
        csock[ix] = -1;
        csock_sts[ix] = 0;
        sock_count--;
        printf("connection closed sock_count=%d\n",sock_count);
        return;
    }
    //最後の文字が改行なら改行を削除する
    len = strlen(rbuf);
    if (rbuf[len-1] == '\n'){
        rbuf[len-1] = '\0';
    }
    //ユーザー未登録なら
    if (csock_sts[ix] == 0){
        //ユーザ名受信処理
        receive_user(ix,rbuf);
    }else{
        //テキスト受信処理
        receive_text(ix,rbuf);
    }
}
//ユーザ名受信処理
void receive_user(int ix,char rbuf[])
{
    int ret;
    char    usr_reg[] = "USERNAME REGISTERED\n";
    char    usr_rej[] = "USERNAME REJECTED\n";
    //ユーザー名が未受信かチェック
    ret = check_user(rbuf);
    if (ret == 0){
        //ユーザー名未受信の場合
        printf("registered user=%s\n",rbuf);
        strcpy(USERNAME[ix],rbuf);
        write(csock[ix],usr_reg,strlen(usr_reg));
        csock_sts[ix] = 1;
    }else{
        //ユーザー名重複又は不正なら、そのユーザー名を出力
        printf("duplicate user=%s\n",rbuf);
        write(csock[ix],usr_rej,strlen(usr_rej));
        close(csock[ix]);
        csock[ix] = -1;
        csock_sts[ix] = 0;
        sock_count--;
    }
}
//ユーザー名チェック
//戻り値 =  0:正常
//戻り値 =  1:重複あり
//戻り値 =  2:ユーザー名不正
int check_user(char rbuf[])
{
    int i;
    if (strlen(rbuf) > USERNAMELEN) return 2;
    for (i = 0; i < MAXCLIENTS; i++){
        //ユーザー名受信済みのソケット
        if (csock_sts[i] == 1){
            //そのユーザー名に一致するなら、重複エラー
            if (strcmp(rbuf,USERNAME[i])==0) return 1;
        }
    }
    return 0;
}
//テキスト受信処理
void receive_text(int ix,char rbuf[])
{
    int     i;
    char    sbuf[256];
    strcpy(sbuf,"<");
    strcat(sbuf,USERNAME[ix]);
    strcat(sbuf,">");
    strcat(sbuf,rbuf);
    strcat(sbuf,"\n");
    //全クライアントへ配信
    for (i = 0;i < MAXCLIENTS;i++){
        if (csock_sts[i] == 1){
            write(csock[i],sbuf,strlen(sbuf));
        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/08 23:01

    ご教示誠にありがとうございます。参考にさせていただきます。

    キャンセル

0

if(strcat(USERNAME[j],Nbuf)==0){

strcmp と strcat を間違えていませんか.

read は一度の読み込みで何バイト取り込むか分かりません.
例えば, 送信側は 10 バイト一度に送信(write)していても, read 側では最初に 1 バイト, 次に 4 バイト, 次に 2 バイト, 次に 3 バイトとなる可能性があります.
どうやら改行コードが区切りの様ですので, 改行コードがバッファに出現するまで read を繰り返す(バッファに追記していく)必要があります.

strcat の使い方が不自然です. strcat は第一引数の文字列の '\0' の位置から, 第二引数の文字列を追記します.

char nll[]="\0";
strcat(USERNAME[i],nll);


直前の strncpy で USERNAME に '\0' が入っていない場合を考えて追加しているつもりのようですが, strcat が正常に動くなら '\0' が入っていることになり, 意味ありません.

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/08 16:18

    strcatとstrcmpを確かに間違えていました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る