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

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

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

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

GCC

GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

Q&A

解決済

1回答

887閲覧

構造体配列の扱い方 格納と削除

Patao_program

総合スコア22

C

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

GCC

GCCはGNU Compiler Collectionの略です。LinuxのC言語コンパイラのデファクトスタンダードであり、数多くの他言語やプラットフォームサポートもします。

0グッド

0クリップ

投稿2022/12/14 03:35

編集2022/12/16 04:50

前提

チャットのサーバー側の処理を作成しています。
ソケット通信で取得した、クライアントの情報を格納する配列(struct Client clients[MAX_CONNECTIONS];)を定義しています。
配列内の、クライアント情報のポインタ(struct Client
)は、接続時に、mallocで動的に割り当てています。

実現したいこと

クライアントが接続してきたら、配列にユーザー情報を格納、切断した場合ユーザー情報削除をしたいです。
どのような実装が最適でしょうか?また、下記のような実装でも問題はないでしょうか?

該当のソースコード

C

1#define MAX_CONNECTIONS 100 2 3〜省略 4// クライアントの情報の構造体 5struct Client 6{ 7 int socket; // acceptで得たソケット 8 pthread_t thread; // ユーザーのスレッド 9  int number; // Client *clientsの配列の添字 10 char *name; // 登録した名前 11 bool is_registered; // 登録したかどうか 12}; 13 14struct Clients 15{ 16 struct Client *clients[MAX_CONNECTIONS]; //クライアント情報を格納する構造体のポインタ配列 17 int count; //現在の接続数 18}; 19〜省略 20 21 22/// ソースコード全文 23#include <stdio.h> 24#include <stdlib.h> 25#include <unistd.h> 26#include <sys/socket.h> 27#include <netinet/in.h> 28#include <arpa/inet.h> 29#include <pthread.h> 30#include <stdbool.h> 31#include <string.h> 32 33#define USEAGE "Usage: chat_server -p PORT\n" 34#define MAX_CONNECTIONS 100 35 36struct Client 37{ 38 int socket; 39 pthread_t thread; 40 int number; 41 char *name; 42 bool is_registered; 43}; 44 45struct Clients 46{ 47 struct Client *clients[MAX_CONNECTIONS]; 48 int count; 49}; 50 51static int listen_requests(int listen_port); 52static void *recv_message(void *void_client); 53static void analysis_message(char *message, struct Client *client); 54static bool has_name(char *name); 55static int push_client(struct Client *client); 56static void send_message(struct Client *client, char *message); 57static void broadcast_message(struct Client *client, char *message); 58static void *xmalloc(size_t size); 59static void free_client(struct Client *client); 60static void die(char *message); 61 62static struct Clients clients_info = {0}; 63static pthread_mutex_t mutex; 64 65int main(int argc, char *argv[]) 66{ 67 int port, c, socket_fd, c_socket_fd; 68 69 if (argc < 3) 70 { 71 fprintf(stderr, USEAGE); 72 exit(1); 73 } 74 75 while ((c = getopt(argc, argv, "p:")) != -1) 76 { 77 switch (c) 78 { 79 case 'p': 80 port = atoi(optarg); 81 82 if (port < 1024 || port > 65535) 83 { 84 fprintf(stderr, "ポート番号は、1024から65535の間で指定してください。\n"); 85 exit(1); 86 } 87 break; 88 case '?': 89 fprintf(stderr, USEAGE); 90 exit(1); 91 } 92 } 93 pthread_mutex_init(&mutex, NULL); 94 socket_fd = listen_requests(port); 95 96 while (1) 97 { 98 if ((c_socket_fd = accept(socket_fd, NULL, NULL)) == -1) 99 die("accept(2)"); 100 101 struct Client *client = NULL; 102 client = xmalloc(sizeof(struct Client)); 103 memset(client, 0, sizeof(struct Client)); 104 105 client->socket = c_socket_fd; 106 client->name = NULL; 107 client->is_registered = false; 108 client->number = push_client(client); 109 110 pthread_create(&client->thread, NULL, recv_message, client); 111 pthread_detach(client->thread); 112 } 113 114 pthread_mutex_destroy(&mutex); 115 116 return 0; 117} 118 119static void *recv_message(void *void_client) 120{ 121 struct Client *client = void_client; 122 int recv_size; 123 char recv_buf[1024]; 124 char *send_text; 125 126 printf("%d\n", client->number); 127 128 while (1) 129 { 130 recv_size = recv(client->socket, recv_buf, sizeof recv_buf, 0); 131 if (recv_size == -1) 132 die("recv"); 133 else if (recv_size == 0) 134 { 135 136 send_text = xmalloc(strlen(client->name) + 100); 137 sprintf(send_text, "MESSAGE %sさんが、チャットから退出しました。\n", client->name); 138 broadcast_message(client, send_text); 139 free(send_text); 140 free_client(client); 141 return NULL; 142 } 143 else 144 analysis_message(recv_buf, client); 145 } 146 147 return NULL; 148} 149 150static void analysis_message(char *message, struct Client *client) 151{ 152 char *method; 153 char *send_text; 154 char *m; 155 char *tmp; 156 157 tmp = xmalloc(strlen(message) + 1); 158 strcpy(tmp, message); 159 printf("%s\n", message); 160 161 /* 162 chat プロトコル 163 164 ユーザー登録 165 "CONNECT username", 166 "CONNECT statuscode status_message" 167 メッセージ送信・受信 168 "MESSAGE (BODY)" 169 170 */ 171 172 m = strchr(message, ' '); 173 *m++ = '\0'; 174 method = xmalloc(m - message); 175 176 strcpy(method, message); 177 // message = m; 178 179 if (!strcmp("CONNECT", method) && !client->is_registered) 180 { 181 if (!has_name(m)) 182 { 183 pthread_mutex_lock(&mutex); 184 client->name = xmalloc(strlen(m) + 1); 185 strcpy(client->name, m); 186 pthread_mutex_unlock(&mutex); 187 client->is_registered = true; 188 send_text = xmalloc(strlen("CONNECT 200 OK") + 1); 189 strcpy(send_text, "CONNECT 200 OK"); 190 send_message(client, send_text); 191 send_text = realloc(send_text, strlen(client->name) + 100); 192 if (send_text == NULL) 193 die("realloc(3)"); 194 sprintf(send_text, "MESSAGE %sさんが、チャットに参加しました。\n", client->name); 195 broadcast_message(client, send_text); 196 free(send_text); 197 } 198 else 199 { 200 send_text = xmalloc(strlen("CONNECT 400 The name is exists") + 1); 201 strcpy(send_text, "CONNECT 400 The name is exists"); 202 send_message(client, send_text); 203 free(send_text); 204 } 205 } 206 else if (!strcmp("MESSAGE", method) && client->is_registered) 207 { 208 send_text = xmalloc(strlen(m) + 1); 209 strcpy(send_text, tmp); 210 broadcast_message(client, send_text); 211 free(send_text); 212 } 213 // else 214 // { 215 // } 216 217 printf("%s\n", method); 218 free(tmp); 219 free(method); 220} 221 222static int listen_requests(int listen_port) 223{ 224 int socket_fd; 225 struct sockaddr_in addr; 226 227 socket_fd = socket(AF_INET, SOCK_STREAM, 0); 228 229 if (socket_fd < 0) 230 die("socket(2)"); 231 232 addr.sin_family = AF_INET; // インターネットドメイン(IPv4) 233 addr.sin_addr.s_addr = INADDR_ANY; // 全てのアドレスからの接続を受け入れる(=0.0.0.0) 234 addr.sin_port = htons(listen_port); 235 236 if (bind(socket_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1) 237 die("bind(2)"); 238 239 if (listen(socket_fd, MAX_CONNECTIONS) == -1) 240 die("listen(2)"); 241 242 return socket_fd; 243} 244 245static int push_client(struct Client *client) 246{ 247 int client_num, i; 248 249 pthread_mutex_lock(&mutex); 250 251 for (i = 0; i < MAX_CONNECTIONS; i++) 252 { 253 if (clients_info.clients[i] == NULL) 254 { 255 clients_info.clients[i] = client; 256 client_num = i; 257 break; 258 } 259 } 260 261 clients_info.count++; 262 263 pthread_mutex_unlock(&mutex); 264 265 return client_num; 266} 267 268static bool has_name(char *name) 269{ 270 int i; 271 pthread_mutex_lock(&mutex); 272 273 for (i = 0; i < MAX_CONNECTIONS; i++) 274 { 275 if (clients_info.clients[i] != NULL && clients_info.clients[i]->name != NULL) 276 { 277 if (!strcmp(clients_info.clients[i]->name, name)) 278 return true; 279 } 280 } 281 282 pthread_mutex_unlock(&mutex); 283 284 return false; 285} 286 287static void send_message(struct Client *client, char *message) 288{ 289 ssize_t size; 290 291 pthread_mutex_lock(&mutex); 292 293 size = send(client->socket, message, strlen(message) + 1, 0); 294 295 pthread_mutex_unlock(&mutex); 296 if (size < 0) 297 die("send(2)"); 298} 299 300static void broadcast_message(struct Client *client, char *message) 301{ 302 int i; 303 304 pthread_mutex_lock(&mutex); 305 306 for (i = 0; i < MAX_CONNECTIONS; i++) 307 { 308 if (clients_info.clients[i] != NULL) 309 { 310 ssize_t size; 311 312 size = send(clients_info.clients[i]->socket, message, strlen(message) + 1, 0); 313 if (size < 0) 314 die("send(2)"); 315 } 316 } 317 318 pthread_mutex_unlock(&mutex); 319} 320 321static void free_client(struct Client *client) 322{ 323 pthread_mutex_lock(&mutex); 324 clients_info.clients[client->number] = NULL; 325 clients_info.count--; 326 free(client->name); 327 free(client); 328 pthread_mutex_unlock(&mutex); 329} 330 331static void *xmalloc(size_t size) 332{ 333 void *p; 334 p = malloc(size); 335 336 if (!p) 337 die("malloc(3)"); 338 339 return p; 340} 341 342static void die(char *message) 343{ 344 perror(message); 345 exit(1); 346} 347

考えたこと

私が、考えたのは、ユーザー情報を格納した配列の添字を覚えておき、削除するときは、freeでクライアント情報のメモリを開放、クライアント情報の配列の記憶した添字番号に格納されているポインタを0で上書きする。
配列内に情報を格納するときは、for文で、ポインタが初期化されている要素を探し、空いていれば格納する。

というように考えています。

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

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

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

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

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

jimbe

2022/12/14 04:04 編集

実装と言って宣言だけ出されても何も分かりません。 省略された部分に考えたことが全て書かれており、動作させて問題が発生したのでしたら、その問題をお書きください。
Patao_program

2022/12/14 04:10

説明不足で、申し訳ありません。 実際には、まだコードとしては、配列へのクライアント情報の格納と削除の箇所は、書いてはいません。 実装するときの、考え方としてあっているのかを確かめたかったです。
jimbe

2022/12/14 07:58 編集

チャットのサーバで何が最適かを知っている人はいないでしょう。(目玉焼きは何が最適かと似たようなことです。) プログラムはデータ構造とコードの双方が噛み合って意味があるもので、一方が良くても他方がダメなら両方ダメと同じです。 情報の格納と削除の方法を最適かと判断するには、それらを行うタイミングや必要な情報等が関わります。 極一部の考え方だけ問題が無かったとしても、他で問題があれば全体として動作しないでしょうし、実装に問題があったり使い方を間違えていれば動作しないでしょう。 そもそも何故この配列が必要なのでしょうか。 本当に極一部だけの問題で、チャット云々は関係無くもっと一般化して可変長配列を c でとか言うお話なら、質問内容をそのようにされたほうが良いと思います。
Zuishin

2022/12/14 06:17

「添字を覚えておき」というのがネックになりはしないでしょうか。 セッション情報をクッキーに保存し、配列の代わりに連想配列を使う方がなにかと便利そうです。 仮に 10 番のユーザーがログアウトし、空いた 10 番に別のユーザーがログインした後、前の 10 番が再び接続してきた場合、それらを区別するのは数値の添字では不十分になるかもしれません。
Patao_program

2022/12/16 03:44

jimbeさん >本当に極一部だけの問題で、チャット云々は関係無くもっと一般化して可変長配列を c でとか言うお話なら、質問内容をそのようにされたほうが良いと思います。 極一部の問題でしたので、そのほうが、良かったです。 Zuishinさん >仮に 10 番のユーザーがログアウトし、空いた 10 番に別のユーザーがログインした後、前の 10 番が再び接続してきた場合、それらを区別するのは数値の添字では不十分になるかもしれません。 切断時に、10番の情報は完全に削除し、前の10番には、新たな接続時に、新たな添字が割り当てるようにしているので、添字で管理しても、問題はないと思いました。
Zuishin

2022/12/16 03:50

何も問題ないと思っているなら、聞かずに実装すべきなのでは? 質問はなんでしょう?
jimbe

2022/12/16 04:09

サーバのメイン構造が(よくある)『 accept してソケットをスレッド(orプロセス)に任せてまた accept 待ちに戻る』の繰り返しであれば、ソケットも登録のフラグもスレッドの参照も保持は不要でしょう。 後はユーザ名の保持をどうするかだけで、ソケット毎のスレッドが使うだけであればそちらに持たせればよいわけで、サーバ(プロセス)が持つ必要は無いのでは。
Patao_program

2022/12/16 04:25

実装について、確信がないので、質問してます。
jimbe

2022/12/16 04:31

やり方は人各々ではありますが、 teratail は、まず自身でやってみて具体的な問題があったら質問する形が推奨されています。
Zuishin

2022/12/16 04:38

問題ないという確信があるようなので聞いています。
Patao_program

2022/12/16 04:52

登録フラグは、名前があるかで判断できるので、いらなかったですね。 スレッドに関しても、いらないですね。 ソケットは、複数人チャットを想定していて、送信処理するときに、他のユーザーのソケットを知る必要があるので、保持する必要性はあるのではないでしょか?
Patao_program

2022/12/16 04:53

>具体的な問題があったら質問する形が推奨されています。 それは、知りませんでした。
jimbe

2022/12/16 05:47

>ソケットは、複数人チャットを想定していて、送信処理するときに、他のユーザーのソケットを知る必要がある 名前を指定して(他人に見せずに)直接送る感じの機能でしょうか。 その仕様は知りませんでした。 ただその為に直接ソケットを使ってしまうと、ストリーム上で他の人の発言と(バイト単位で)混ざってしまいそうですが、大丈夫でしょうか。
guest

回答1

0

ベストアンサー

私が、考えたのは、...

それで問題ないと思います。配列の添字はスレッドに渡しておけばいいですね。
C++ を使っていいのでしたら、配列ではなく std::vector<std::unique_ptr<Client>> にするとより簡単でしょう。

投稿2022/12/14 04:02

int32_t

総合スコア20882

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問