前提・実現したいこと
現在、複数のクライアントを受け付けるチャットプログラムをC言語で作成しています。クライアントを受け付けると文字列(REQUEST ACCEPTED)を返し、クライアントの引数のユーザー名を登録しています。今まで登録したユーザー名と一致した場合はUSERNAME REJECTEDと返し、そうでない場合はUSERNAME REFISTERDと返します。ユーザー登録ができれば、チャットプログラムが開始されEOFまでクライアントからのメッセージを受け付け続けます。
発生している問題・エラーメッセージ
ユーザー登録を終えた(無事USERNAME REGISTERDが返ってきた)後に、その登録した名前がサーバー側の端末で大量に表示されてサーバープログラムが終了してしまいます。以下はユーザー名をhogeにした場合のエラーメッセージとなっています。
hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge...
該当のソースコード
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 MAXCLIENTS 5 14#define USERNAMELEN 31 15int main(int argc,char **argv) 16{ 17 int sock,h=0,k=5,csock[5];/*クライアントを受け付けたソケット*/ 18 struct sockaddr_in clt; 19 struct sockaddr_in svr; 20 struct hostent *cp; 21 int clen,nbytes,fdmax,flag; 22 int Nbyte[5]; //登録用の配列 23 char USERNAME[MAXCLIENTS][USERNAMELEN+1]; //名前は最大31文字 24 char Nbuf[32]; 25 char srv_mes[17] = "REQUEST ACCEPTED\n"; 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); //文字配列Nbufに名前をコピー 80 Nbyte[i]=nbytes; 81 flag=0; 82 for(int j=0;j<MAXCLIENTS;j++){ 83 if(strcmp(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 } 91 close(csock[i]); 92 goto section3; 93 } 94 } 95 if(flag==0){ // 初めての名前の場合 96 write(csock[i],"USERNAME REGISTERED\n",20); 97 strncpy(USERNAME[i],Nbuf,Nbyte[i]-1); 98 //char nll[]="\0"; 99 //strcat(USERNAME[i],nll); 100 write(1,USERNAME[i],sizeof(USERNAME[i])); 101 k++; 102 goto section3; 103 } 104 } 105 }//ここまでfor文の内容 106 } 107 for(int i=0;i<MAXCLIENTS;i++){ 108 if(FD_ISSET(csock[i],&rfds)){ 109 if(read(csock[i],rbuf,sizeof(rbuf))!=0){ 110 char user[64]="<"; 111 strcat(user,USERNAME[i]); 112 //strcat(user,"\0"); 113 strcat(user,">"); 114 strcat(user,rbuf); 115 // 以下ですべてのクライアントにユーザー名を送信する 116 for(int j=0;j<MAXCLIENTS;j++){ 117 write(csock[j],user,sizeof(user)); 118 } 119 }else{ 120 perror("read error."); 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 close(sock); 133 exit(0); 134}
補足情報(FW/ツールのバージョンなど)
開発環境はUbuntuです。
> その登録した名前がサーバー側の端末で大量に表示されてサーバープログラムが終了してしまいます
その現象に付きまして、何が起きていると思われてどのような情報収集・処置等をお試しになられましたか。
もしくは、そのように「原因を探す方法」が分からないということでしょうか。
何が起きているかが分からず、その原因について質問させていただきました。コードのどこが悪さをしているのかが分からない状況です。
今いろいろ試してみてread関数の部分をいじればユーザー名が複数表示されるバグは修正されました。しかし、次は"_vdso_get"というエラー(?)表示が出てサーバーが終了してしまいました。
このエラーとは何を意味するのでしょうか。
ご提示のコードはご自身で書かれたものでは無いのでしょうか。1行1行追ってデバッグされていますか?
Windows/MinGW にてコードを見させて頂いていますが、"綱渡り"で動作しているような感じが致します。
ですので read 関数の部分を弄ったとされても、どうプログラムの動作が変わったかを推測することは難しいです。
どこから手を付けるかも難しいですが、例えば変数名はその意味する適切な名前を付けることで読み易く・間違えにくくなります。また {} が何重にも重なるとどこからどこまでがどの対象になるのかが分かりにくくなります。
ある程度の処理単位でモジュール化(関数化)し、引数・戻り値でデータをやり取りするようにすると、どこでどのデータが必要でどのデータが更新されるのかが分かり易くなります。
使っていない変数が宣言されていたり、MAXCLIENTS があるのに 5 という数字が散見されます。コメントが処理と合っていなかったり、コメントに // と /*~*/ が混在していたりします。(コメントは通常には // を使うようにすると、バグ調査等で一時的に /* */ で処理を消すことが出来るようになります。)
各システムコールの仕様ももう少し確認されたほうが良いかと思います。
select は動作後 rfds を更新するのではないでしょうか。 goto section3 でループする場合 rfds の再設定が必要だったように思います。
write の第三引数は、第二引数から何バイト送るかを指定するはずですが、第二引数の変数のサイズ(sizeof()) を指定しては、文字列以降の不明データを送ってしまうのではないでしょうか。逆に(?) 18 等と手動で文字数を数えた数を書いていたりします。どちらも strlen を使う場面と思います。
read は、必ずしもクライアントが write した単位(行?)で受信するとは限りません。最悪(ネットワークが"重い"などで)1バイトずつしか受信しない場合もあります。read 中に接続が切れてエラーが発生する場合もあります。それでも(想定通り)動作するでしょうか。
現在の異常動作と直接関係ないように思われるでしょうけれど、部分の出来が怪しいほど、"全体の出来" も怪しくなります。
読み難い・分かり難いコードは、読む人の動作確認・見直し・修正を難しくします。これには作った本人も含まれるのです。(「昨日の自分は今日の自分では無い」という自戒(?)の言葉も存在します。)
長々と失礼しましたが、私としましては「まず、全体整理から」をお勧めします。(こちらでも整理・修正を試みています。)