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

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

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

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

Q&A

1回答

3929閲覧

複数クライアント受付のチャットプログラム(C言語)

karashi_wasabi

総合スコア0

C

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

0グッド

0クリップ

投稿2021/07/26 03:01

前提・実現したいこと

現在、複数のクライアントを受け付けるチャットプログラムを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です。

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

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

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

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

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

jimbe

2021/07/26 03:12

> その登録した名前がサーバー側の端末で大量に表示されてサーバープログラムが終了してしまいます その現象に付きまして、何が起きていると思われてどのような情報収集・処置等をお試しになられましたか。 もしくは、そのように「原因を探す方法」が分からないということでしょうか。
karashi_wasabi

2021/07/26 04:21

何が起きているかが分からず、その原因について質問させていただきました。コードのどこが悪さをしているのかが分からない状況です。
karashi_wasabi

2021/07/26 04:35

今いろいろ試してみてread関数の部分をいじればユーザー名が複数表示されるバグは修正されました。しかし、次は"_vdso_get"というエラー(?)表示が出てサーバーが終了してしまいました。 このエラーとは何を意味するのでしょうか。
dodox86

2021/07/26 05:16

ご提示のコードはご自身で書かれたものでは無いのでしょうか。1行1行追ってデバッグされていますか?
jimbe

2021/07/26 05:30

Windows/MinGW にてコードを見させて頂いていますが、"綱渡り"で動作しているような感じが致します。 ですので read 関数の部分を弄ったとされても、どうプログラムの動作が変わったかを推測することは難しいです。 どこから手を付けるかも難しいですが、例えば変数名はその意味する適切な名前を付けることで読み易く・間違えにくくなります。また {} が何重にも重なるとどこからどこまでがどの対象になるのかが分かりにくくなります。 ある程度の処理単位でモジュール化(関数化)し、引数・戻り値でデータをやり取りするようにすると、どこでどのデータが必要でどのデータが更新されるのかが分かり易くなります。 使っていない変数が宣言されていたり、MAXCLIENTS があるのに 5 という数字が散見されます。コメントが処理と合っていなかったり、コメントに // と /*~*/ が混在していたりします。(コメントは通常には // を使うようにすると、バグ調査等で一時的に /* */ で処理を消すことが出来るようになります。) 各システムコールの仕様ももう少し確認されたほうが良いかと思います。 select は動作後 rfds を更新するのではないでしょうか。 goto section3 でループする場合 rfds の再設定が必要だったように思います。 write の第三引数は、第二引数から何バイト送るかを指定するはずですが、第二引数の変数のサイズ(sizeof()) を指定しては、文字列以降の不明データを送ってしまうのではないでしょうか。逆に(?) 18 等と手動で文字数を数えた数を書いていたりします。どちらも strlen を使う場面と思います。 read は、必ずしもクライアントが write した単位(行?)で受信するとは限りません。最悪(ネットワークが"重い"などで)1バイトずつしか受信しない場合もあります。read 中に接続が切れてエラーが発生する場合もあります。それでも(想定通り)動作するでしょうか。 現在の異常動作と直接関係ないように思われるでしょうけれど、部分の出来が怪しいほど、"全体の出来" も怪しくなります。 読み難い・分かり難いコードは、読む人の動作確認・見直し・修正を難しくします。これには作った本人も含まれるのです。(「昨日の自分は今日の自分では無い」という自戒(?)の言葉も存在します。) 長々と失礼しましたが、私としましては「まず、全体整理から」をお勧めします。(こちらでも整理・修正を試みています。)
guest

回答1

0

完成度が低いですが、Windows/Cygwin で動かすのにも手間取りまして時間的にということで出させて頂きます。
一応( java で)クライアントを作りまして 2 クライアントではテストをしましたので、機能面はそれなり大丈夫かと思います。

行区切りを改行とし CRLF としましたが、Ubuntu なら CR でしょうか。

c

1#include <sys/types.h> 2#include <sys/time.h> 3#include <unistd.h> 4#include <string.h> 5#include <stdio.h> 6#include <stdlib.h> 7 8#include <sys/socket.h> 9#include <sys/wait.h> 10#include <netinet/in.h> 11#include <netdb.h> 12 13#ifndef NULL 14#define NULL (void*)0 15#endif 16 17#define MAXCLIENTS 5 18 19//名前は最大31文字 20#define USERNAMELEN 31 21 22const char CRLF[] = {'\r','\n'}; 23 24typedef struct clientinfo { 25 int sockfd; 26 void (*process)(struct clientinfo *, int, int); 27 int rbuflen; //rbuf の有効データ長 28 char name[USERNAMELEN+1]; 29 char line[128]; 30 char rbuf[1024]; //受信バッファ 31} CLIENTINFO; 32 33void receiveProcess(CLIENTINFO infos[], int size, int index); 34 35void clearUserInfo(CLIENTINFO *info) { 36 memset(info, 0, sizeof(CLIENTINFO)); 37 info->sockfd = -1; 38 info->process = receiveProcess; 39} 40 41int sendMessage(int sockfd, char *message) { 42 write(sockfd, message, strlen(message)); 43 write(sockfd, CRLF, sizeof(CRLF)); 44 return 0; 45} 46 47/** 48 * クライアントとの接続の処理を行う。 49 * clientinfos 配列の空きを探し、あれば設定する。 50 */ 51void acceptProcess(CLIENTINFO infos[], int size, int index) { 52 struct sockaddr_in addr; 53 int addrlen; 54 int sockfd; 55 56 addrlen = sizeof(addr); 57 if((sockfd = accept(infos[index].sockfd, (struct sockaddr *)&addr, &addrlen)) < 0) { 58 perror("accept error"); 59 exit(2); 60 } 61 62 CLIENTINFO *client = NULL; 63 for(int i=1; i<size; i++) { 64 if(infos[i].sockfd == -1) { 65 client = &infos[i]; 66 break; 67 } 68 } 69 70 if(client == NULL) { 71 sendMessage(sockfd, "CLIENTS LIMIT"); 72 close(sockfd); 73 return; 74 } 75 76 sendMessage(sockfd, "REQUEST ACCEPTED"); 77 client->sockfd = sockfd; 78} 79 80/** 81 * sockfd から読み込み、バッファに追加する。 82 * 改行が含まれていれば1行分を line に切り出し(改行は削除)、line の有効長を返す。 83 */ 84int receive(CLIENTINFO *info) { 85 if(info->rbuflen == sizeof(info->rbuf)) { 86 perror("read buffer overflow"); 87 return -1; 88 } 89 int len = read(info->sockfd, info->rbuf, sizeof(info->rbuf)-info->rbuflen); 90 if(len < 0) { 91 perror("read error"); 92 return -1; 93 } 94 //改行があったら、rbuf から line を切り出す 95 info->rbuflen += len; 96 for(int i=0; i<info->rbuflen-1; i++) { 97 if(info->rbuf[i] == CRLF[0] && info->rbuf[i+1] == CRLF[1]) { 98 info->rbuf[i] = '\0'; 99 strcpy(info->line, info->rbuf); 100 i += sizeof(CRLF); //改行後の文字列の先頭 101 //前詰め 102 int j = 0; 103 while(i < info->rbuflen) info->rbuf[j++] = info->rbuf[i++]; 104 info->rbuflen = j; 105 return strlen(info->line); 106 } 107 } 108 return 0; 109} 110 111void sendMessageToAll(char *msg, CLIENTINFO infos[], int size) { 112 printf("%s\n", msg); 113 for(int i=1; i<size; i++) { 114 if(infos[i].sockfd >= 0) { 115 sendMessage(infos[i].sockfd, msg); 116 } 117 } 118} 119 120/** 121 * クライアントとの接続後の処理を行う。 122 * 接続直後の受信はユーザ名と見なし、内部に保存する。 123 * 以降の受信はユーザ名と共にメッセージとして全ユーザに送信する。 124 */ 125void receiveProcess(CLIENTINFO infos[], int size, int index) { 126 int n = receive(&infos[index]); 127 if(n < 0) { //異常発生 128 close(infos[index].sockfd); //切断 129 char msg[256]; 130 sprintf(msg, "<%s> Disconnected..", infos[index].name); 131 clearUserInfo(&infos[index]); 132 sendMessageToAll(msg, infos, size); 133 return; 134 } 135 if(n == 0) return; //受信したが1行に足りなかった 136 137 //1行(以上)受信 138 if(strlen(infos[index].name) == 0) { //名前がまだ無い=接続したばかり 139 //同一名が無ければ登録 140 int found = 0; 141 for(int i=1; i<size && !found; i++) { 142 found = !strcmp(infos[i].name, infos[index].line); 143 } 144 if(found) { 145 sendMessage(infos[index].sockfd, "THE NAME IS USED."); 146 sendMessage(infos[index].sockfd, "PLEASE NEW NAME."); 147 return; 148 } 149 memset(infos[index].name, 0, sizeof(infos[index].name)); 150 strncpy(infos[index].name, infos[index].line, sizeof(infos[index].name)-1); 151 152 sendMessage(infos[index].sockfd, "USERNAME REGISTERED"); 153 strcpy(infos[index].line, "Connected"); 154 } 155 //全クライアントにメッセージとして送信 156 char msg[256]; 157 sprintf(msg, "<%s> %s", infos[index].name, infos[index].line); 158 sendMessageToAll(msg, infos, size); 159} 160 161/** 162 * 接続待ちソケットを生成 163 */ 164int bindSocket(int port, int backlog) { 165 int sockfd; 166 167 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 168 perror("socket"); 169 return -1; 170 } 171 172 struct sockaddr_in svr; 173 memset(&svr, 0, sizeof(svr)); 174 svr.sin_family = AF_INET; 175 svr.sin_addr.s_addr = htonl(INADDR_ANY); 176 svr.sin_port = htons(port); 177 if(bind(sockfd, (struct sockaddr *)&svr, sizeof(svr)) < 0) { 178 perror("bind"); 179 close(sockfd); 180 return -1; 181 } 182 183 if(listen(sockfd, backlog) < 0) { 184 perror("listen"); 185 close(sockfd); 186 return -1; 187 } 188 189 return sockfd; 190} 191 192/** 193 * infos の中の有効な sockfd を select し、接続/読み込み可能になったものに対して 194 * process を呼び出す。 195 */ 196int selectReceive(CLIENTINFO infos[], int size) { 197 int fdsetCount = 0; 198 199 int fdmax = -1; 200 fd_set rfds; 201 FD_ZERO(&rfds); 202 for(int i=0; i<size; i++) { 203 if(infos[i].sockfd >= 0) { 204 FD_SET(infos[i].sockfd, &rfds); 205 fdsetCount ++; 206 fdmax = fdmax >= infos[i].sockfd ? fdmax : infos[i].sockfd; 207 } 208 } 209 210 struct timeval timeout; 211 timeout.tv_sec = 10; 212 timeout.tv_usec = 0; 213 214 printf("select fdsetCount=%d\n", fdsetCount); 215 int n = select(fdmax+1, &rfds, NULL, NULL, &timeout); 216 if(n > 0) { 217 for(int i=0; i<size; i++) { 218 if(FD_ISSET(infos[i].sockfd, &rfds)) { 219 printf("process index=%d\n", i); 220 infos[i].process(infos, size, i); 221 } 222 } 223 } else { 224 //if(errno == EINTR) return 1; 225 } 226 227 return !0; 228} 229 230int main(int argc, char **argv) { 231 printf("initialize.\n"); 232 CLIENTINFO clientinfos[1+MAXCLIENTS]; //0=接続待ち,1~=クライアント 233 for(int i=0; i<=MAXCLIENTS; i++) clearUserInfo(&clientinfos[i]); 234 235 printf("bind socket.\n"); 236 clientinfos[0].process = acceptProcess; 237 if((clientinfos[0].sockfd = bindSocket(10140, MAXCLIENTS)) < 0) { 238 exit(1); 239 } 240 241 printf("loop start.\n"); 242 while(1) { 243 selectReceive(clientinfos, sizeof(clientinfos)/sizeof(CLIENTINFO)); 244 } 245 246 close(clientinfos[0].sockfd); 247 exit(0); 248}

投稿2021/07/27 16:06

jimbe

総合スコア12545

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問