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

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

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

Q&A

解決済

2回答

2633閲覧

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

tosakasimon

総合スコア8

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

0グッド

0クリップ

投稿2019/08/05 06:06

編集2019/08/08 19:01

複数クライアント接続

###問題

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

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

c

1#include <sys/types.h> 2#include <sys/socket.h> 3#include <sys/time.h> 4#include <sys/wait.h> 5#include <netinet/in.h> 6#include <netdb.h> 7#include <unistd.h> 8#include <sys/wait.h> 9#include <string.h> 10#include <stdio.h> 11#include <stdlib.h> 12#define BUFSIZE 256 13#define TIMEOUT 10 14#define MAXCLIENTS 5 15void myalarm(int sec); 16void timeout(); 17int main(int argc,char **argv) 18{ 19 int sock,h=0,k=5,csock[5];/*クライアントを受け付けたソケット*/ 20 struct sockaddr_in clt; 21 struct sockaddr_in svr; 22 struct hostent *cp; 23 int clen,nbytes,fdmax,flag; 24 int Nbyte[5]; 25 char USERNAME[5][32],Nbuf[32]; 26 fd_set rfds;/* select()で用いるファイル記述子集合*/ 27 struct timeval tv; 28 char rbuf[128],buffer[128],*arg; 29 memset(rbuf, 0, sizeof(rbuf)); 30 for ( int i = 0; i <k; i++ ){ 31 csock[i] = -1;//通信用の配列の初期化 32 } 33 /* select()が返ってくるまでの待ち時間を指定する変数*/ 34 35 if ((sock=socket(AF_INET,SOCK_STREAM,0))<0) { 36 perror("socket"); 37 exit(1); 38 } 39 /*クライアントからの接続待ち受けなど*/ 40 bzero(&svr,sizeof(svr)); 41 svr.sin_family=AF_INET; 42 svr.sin_addr.s_addr=htonl(INADDR_ANY); 43 svr.sin_port=htons(10140); 44 if(bind(sock,(struct sockaddr *)&svr,sizeof(svr))<0) { 45 perror("bind"); 46 exit(1); 47 } 48 if (listen(sock,5)<0) { 49 /*待ち受け数に5を指定*/ 50 perror("listen"); 51 exit(1); 52 } 53 while(1){ 54 FD_ZERO(&rfds); 55 FD_SET(sock,&rfds); 56 fdmax=sock; 57 for(int i=0;i<MAXCLIENTS;i++){ 58 if(csock[i]>0){ 59 if(csock[i]>fdmax){ 60 fdmax=csock[i]; 61 } 62 FD_SET(csock[i],&rfds); 63 } 64 } 65 tv.tv_sec = 1; 66 tv.tv_usec = 0;/*標準入力とソケットからの受信を同時に監視する*/ 67 section3: 68 if(select(fdmax+1,&rfds,NULL,NULL,&tv)>0){ 69 if(FD_ISSET(sock,&rfds)){ 70 for(int i=0;i<MAXCLIENTS;i++){ 71 clen=sizeof(clt); 72 if ( ( csock[i] = accept(sock,(struct sockaddr *)&clt,&clen) ) <0 ) { 73 perror("accept"); 74 exit(2); 75 } 76 if(sock<MAXCLIENTS){ 77 write(csock[i],"REQUEST ACCEPTED\n",17); 78 nbytes=read(csock[i],rbuf,sizeof(rbuf)); 79 strncpy(Nbuf,rbuf,nbytes-1); 80 Nbyte[i]=nbytes; 81 flag=0; 82 for(int j=0;j<MAXCLIENTS;j++){ 83 if(strcat(USERNAME[j],Nbuf)==0){ 84 flag=1; 85 char name[32]="REJECTED"; 86 write(csock[i],"USERNAME REJECTED\n",18); 87 strcat(name,Nbuf); 88 for(int k=0;k<MAXCLIENTS;k++) 89 write(csock[k],name,sizeof(name)); 90 close(csock[i]); 91 goto section3; 92 } 93 } 94 if(flag==0){ 95 write(csock[i],"USERNAME REGISTERED\n",20); 96 strncpy(USERNAME[i],Nbuf,Nbyte[i]-1); 97 char nll[]="\0"; 98 strcat(USERNAME[i],nll); 99 write(1,USERNAME[i],sizeof(USERNAME[i])); 100 k++; 101 goto section3; 102 } 103 104 105 } 106 } 107 } 108 for(int i=0;i<MAXCLIENTS;i++){ 109 if(FD_ISSET(csock[i],&rfds)){ 110 if(read(csock[i],rbuf,sizeof(rbuf))!=0){ 111 char STRING[64]="<"; 112 strcat(STRING,USERNAME[i]); 113 strcat(STRING,"\0"); 114 strcat(STRING,">"); 115 strcat(STRING,rbuf); 116 for(int j=0;j<MAXCLIENTS;j++){ 117 write(csock[j],STRING,sizeof(STRING)); 118 } 119 }else{ 120 fprintf(stderr,"01\n"); 121 close(csock[i]); 122 for(int j=0;MAXCLIENTS;j++){ 123 write(csock[j],USERNAME[i],sizeof(USERNAME[i])); 124 } 125 USERNAME[i][0]='\0'; 126 k--; 127 128 } 129 } 130 } 131 } 132 } 133 close(sock); 134 exit(0); 135}

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

izmktr

2019/08/06 04:48

socketの返り値は確認していますか?0から連番で返ってくることを想定しているようですけど、実際にそうなっていますか?
tosakasimon

2019/08/06 05:03

あれから大幅に進展したので、ソースコードを大幅に書き換えました。
jimbe

2019/08/07 14:09

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

2019/08/08 05:17

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

2019/08/08 07:07

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

回答2

0

ベストアンサー

以下、サンプルコードです。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時に行います。

C

1#include <sys/types.h> 2#include <sys/socket.h> 3#include <sys/time.h> 4#include <sys/wait.h> 5#include <netinet/in.h> 6#include <netdb.h> 7#include <unistd.h> 8#include <sys/wait.h> 9#include <string.h> 10#include <stdio.h> 11#include <stdlib.h> 12#define BUFSIZE 256 13#define TIMEOUT 10 14#define MAXCLIENTS 5 15#define USERNAMELEN 31 16void myalarm(int sec); 17void timeout(); 18//内部関数 19void wait_receive(); 20void check_max_client(int sd); 21int get_empty_ix(void); 22void receive_msg(int ix); 23void receive_user(int ix,char rbuf[]); 24int check_user(char rbuf[]); 25void receive_text(int ix,char rbuf[]); 26//共通変数 27int sock; 28int csock[MAXCLIENTS]; 29int csock_sts[MAXCLIENTS]; //0:ユーザー名未登録 1:ユーザ名登録済み 30int sock_count = 0; 31char USERNAME[MAXCLIENTS][USERNAMELEN+1]; 32int main(int argc,char **argv) 33{ 34 int i; 35 struct sockaddr_in svr; 36 fd_set rfds;/* select()で用いるファイル記述子集合*/ 37 for ( int i = 0; i <MAXCLIENTS; i++ ){ 38 csock[i] = -1;//通信用の配列の初期化 39 csock_sts[i] = 0; 40 } 41 /* select()が返ってくるまでの待ち時間を指定する変数*/ 42 43 if ((sock=socket(AF_INET,SOCK_STREAM,0))<0) { 44 perror("socket"); 45 exit(1); 46 } 47 /*クライアントからの接続待ち受けなど*/ 48 bzero(&svr,sizeof(svr)); 49 svr.sin_family=AF_INET; 50 svr.sin_addr.s_addr=htonl(INADDR_ANY); 51 svr.sin_port=htons(10140); 52 if(bind(sock,(struct sockaddr *)&svr,sizeof(svr))<0) { 53 perror("bind"); 54 exit(1); 55 } 56 if (listen(sock,5)<0) { 57 /*待ち受け数に5を指定*/ 58 perror("listen"); 59 exit(1); 60 } 61 while(1){ 62 //クライアントからの接続及び電文受信の待ち受け 63 wait_receive(); 64 } 65 close(sock); 66 exit(0); 67} 68//クライアントからの接続及び電文受信の待ち受け 69void wait_receive() 70{ 71 struct sockaddr_in clt; 72 struct timeval tv; 73 fd_set rfds;/* select()で用いるファイル記述子集合*/ 74 int fdmax; 75 int ret; 76 int sd; 77 int clen; 78 int i; 79 FD_ZERO(&rfds); 80 FD_SET(sock,&rfds); 81 fdmax=sock; 82 for(int i=0;i<MAXCLIENTS;i++){ 83 if(csock[i]>=0){ 84 if(csock[i]>fdmax){ 85 fdmax=csock[i]; 86 } 87 FD_SET(csock[i],&rfds); 88 } 89 } 90 tv.tv_sec = 1; 91 tv.tv_usec = 0;/*標準入力とソケットからの受信を同時に監視する*/ 92 ret = select(fdmax+1,&rfds,NULL,NULL,&tv); 93 //エラーの場合 94 if (ret < 0){ 95 perror("select"); 96 exit(2); 97 } 98 //タイムアウト 99 if (ret == 0) return; 100 //接続要求の有無 101 if(FD_ISSET(sock,&rfds)){ 102 clen=sizeof(clt); 103 sd = accept(sock,(struct sockaddr *)&clt,&clen); 104 if (sd < 0){ 105 perror("accept"); 106 exit(2); 107 } 108 //最大クライアント数のチェック及びソケットの登録 109 check_max_client(sd); 110 } 111 //電文受信の有無 112 for (i = 0; i < MAXCLIENTS;i++){ 113 if (csock[i] >=0){ 114 if(FD_ISSET(csock[i],&rfds)){ 115 //電文受信処理 116 receive_msg(i); 117 } 118 } 119 } 120 121} 122//最大クライアント数のチェック及びソケットの登録 123void check_max_client(int sd) 124{ 125 int i; 126 char req_acc[] = "REQUEST ACCEPTED\n"; 127 char req_rej[] = "REQUEST REJECTED\n"; 128 if (sock_count < MAXCLIENTS){ 129 //最大クライアント数に達していないならクライアント用ソケットに登録する 130 i = get_empty_ix(); 131 csock[i] = sd; 132 csock_sts[i] = 0; 133 write(sd,req_acc,strlen(req_acc)); 134 sock_count++; 135 printf("connection completed sock_count=%d\n",sock_count); 136 }else{ 137 //最大クライアント数に達しているなら、エラー応答し、ソケットをクローズする。 138 write(sd,req_rej,strlen(req_rej)); 139 close(sd); 140 printf("connection rejected sock_count overflow\n"); 141 } 142} 143//csockの中で空いているソケットの配列の添え字を返す 144int get_empty_ix(void) 145{ 146 int i; 147 for (i = 0; i < MAXCLIENTS;i++){ 148 if (csock[i] == -1) return i; 149 } 150 //バグ以外あり得ないのでアボートする 151 fprintf(stderr,"get_empty_ix error\n"); 152 exit(2); 153} 154//受信電文処理 155void receive_msg(int ix) 156{ 157 int nbytes; 158 char rbuf[128]; 159 int len; 160 memset(rbuf, 0, sizeof(rbuf)); 161 nbytes=read(csock[ix],rbuf,sizeof(rbuf)-1); 162 if (nbytes <= 0){ 163 //受信エラー又はクライアントのソケットクローズの場合 164 if (csock_sts[ix] == 1){ 165 //ユーザー名受信済みなら、ユーザー名を出力 166 printf("closed user=<%s>\n",USERNAME[ix]); 167 } 168 csock[ix] = -1; 169 csock_sts[ix] = 0; 170 sock_count--; 171 printf("connection closed sock_count=%d\n",sock_count); 172 return; 173 } 174 //最後の文字が改行なら改行を削除する 175 len = strlen(rbuf); 176 if (rbuf[len-1] == '\n'){ 177 rbuf[len-1] = '\0'; 178 } 179 //ユーザー未登録なら 180 if (csock_sts[ix] == 0){ 181 //ユーザ名受信処理 182 receive_user(ix,rbuf); 183 }else{ 184 //テキスト受信処理 185 receive_text(ix,rbuf); 186 } 187} 188//ユーザ名受信処理 189void receive_user(int ix,char rbuf[]) 190{ 191 int ret; 192 char usr_reg[] = "USERNAME REGISTERED\n"; 193 char usr_rej[] = "USERNAME REJECTED\n"; 194 //ユーザー名が未受信かチェック 195 ret = check_user(rbuf); 196 if (ret == 0){ 197 //ユーザー名未受信の場合 198 printf("registered user=%s\n",rbuf); 199 strcpy(USERNAME[ix],rbuf); 200 write(csock[ix],usr_reg,strlen(usr_reg)); 201 csock_sts[ix] = 1; 202 }else{ 203 //ユーザー名重複又は不正なら、そのユーザー名を出力 204 printf("duplicate user=%s\n",rbuf); 205 write(csock[ix],usr_rej,strlen(usr_rej)); 206 close(csock[ix]); 207 csock[ix] = -1; 208 csock_sts[ix] = 0; 209 sock_count--; 210 } 211} 212//ユーザー名チェック 213//戻り値 = 0:正常 214//戻り値 = 1:重複あり 215//戻り値 = 2:ユーザー名不正 216int check_user(char rbuf[]) 217{ 218 int i; 219 if (strlen(rbuf) > USERNAMELEN) return 2; 220 for (i = 0; i < MAXCLIENTS; i++){ 221 //ユーザー名受信済みのソケット 222 if (csock_sts[i] == 1){ 223 //そのユーザー名に一致するなら、重複エラー 224 if (strcmp(rbuf,USERNAME[i])==0) return 1; 225 } 226 } 227 return 0; 228} 229//テキスト受信処理 230void receive_text(int ix,char rbuf[]) 231{ 232 int i; 233 char sbuf[256]; 234 strcpy(sbuf,"<"); 235 strcat(sbuf,USERNAME[ix]); 236 strcat(sbuf,">"); 237 strcat(sbuf,rbuf); 238 strcat(sbuf,"\n"); 239 //全クライアントへ配信 240 for (i = 0;i < MAXCLIENTS;i++){ 241 if (csock_sts[i] == 1){ 242 write(csock[i],sbuf,strlen(sbuf)); 243 } 244 } 245} 246

投稿2019/08/08 11:49

編集2019/08/08 12:42
tatsu99

総合スコア5424

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tosakasimon

2019/08/08 14:01

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

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/07 14:51

jimbe

総合スコア12545

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tosakasimon

2019/08/08 07:18

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問