過去ログ 11
過去ログは下にいくほど新しい記事となっております。
※注意:このサイトにあるプログラムのソースコードや、その他の情報はすべて無保証です。やばいと思ったら実行しないでください。コンパイル、実行などによって生じた損害に対する責任は負いませんので、ご了承ください。
SDL_net 3
13-12-2009
2度あることは3度ある。
とりあえず、前回の数当てゲームを多人数プレイ可能にしてみた。これで、ネットプレイの意味が出てきた。
クライアントプログラムです。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define REMOTE_HOST_IP "192.168.0.3" #define REMOTE_HOST_PORT 5555 /* #define DEBUG */ #define SEND_FLAG_EMPTY 0 #define SEND_FLAG_SET 1 #define SEND_FLAG_SENT 2 #define SEND_FLAG_RECV 3 #define ZERO_MEMORY(x, y) memset(x, 0, y) #define TIMEOUT 1000 /* タイムアウト */ /* 送信データセット */ typedef struct { int flag; /* 送信状態のフラグ */ char send_data[1024]; /* 送信データ */ int len; /* 送信データの長さ */ unsigned int time; /* 時間カウント用 */ } t_send_data; /* データタイプ */ typedef struct { char type; char value; char value2; char turnflag; } t_data_type; int copy_send_data(t_send_data*); int set_send_data(char*, int); int set_send_flag(int); int set_send_time(unsigned int); int check_timeout(int); int g_s_thread_alive = 0; /* 送信スレッド生存フラグ */ int g_r_thread_alive = 0; /* 受信スレッド生存フラグ */ int g_comm_thread_alive = 0; /* 監視スレッド生存フラグ */ int g_finished_flag = 0; /* 終了フラグ(監視スレッド停止用) */ int g_input_thread_alive = 0; /* コンソール入力スレッド生存フラグ */ SDL_mutex *g_send_data_mutex; t_send_data g_send_data; /* 送信スレッド 引数はソケット */ int send_thread(void* pointer){ t_send_data data; int result; TCPsocket client_sock = (TCPsocket)pointer; data.flag = SEND_FLAG_EMPTY; while(1){ /* データがセットされるまで待機 */ while(data.flag != SEND_FLAG_SET){ SDL_Delay(20); copy_send_data(&data); } /* printf("send!\n"); */ /* 送信処理 */ result = SDLNet_TCP_Send(client_sock, data.send_data, data.len); if(result < data.len){ printf("送信に失敗しました。\n"); printf("プログラムを終了します。\n"); break; } data.flag = SEND_FLAG_SENT; /* 送信したときの時間を記録 */ set_send_time(SDL_GetTicks()); /* 送信フラグを更新 */ set_send_flag(data.flag); } /* 生存フラグをOFFにしておく */ g_s_thread_alive = 0; return 0; } /* 受信スレッド 引数はソケット */ int recv_thread(void* pointer){ char data[1024]; t_data_type data_type; int result; TCPsocket client_sock; client_sock = (TCPsocket)pointer; #ifdef DEBUG printf("# receive thread start\n"); #endif while(1){ /* 受信まで待機 */ result = SDLNet_TCP_Recv(client_sock, data, sizeof(data) - 1); if(result <= 0){ printf("サーバーとの通信が切断されました。\n"); printf("プログラムを終了します。\n"); break; } data[result] = 0x00; memcpy(&data_type, data, sizeof(data_type)); #ifdef DEBUG printf("#R:t %d, v %d, v2 %d, f %d\n", data_type.type, data_type.value, data_type.value2, data_type.turnflag); #endif switch(data_type.type){ case 11: /* 開始依頼の応答/開始通知 */ printf("ゲーム開始です\n"); set_send_flag(SEND_FLAG_RECV); if(data_type.turnflag == 1){ printf("あなたのターンです。\n"); set_send_flag(SEND_FLAG_EMPTY); } break; case 12: /* 自分入力応答 */ set_send_flag(SEND_FLAG_RECV); printf("あなたが入力した値に対し、サーバーはこう返しました。\n"); switch(data_type.value){ case 's': printf("もっと小さい\n"); break; case 'l': printf("もっと大きい\n"); break; case 'c': printf("正解\n"); printf("あなたの勝ちです\n"); printf("ゲーム終了です\n"); set_send_flag(SEND_FLAG_EMPTY); break; default: break; } break; case 14: /* 他人入力 */ set_send_flag(SEND_FLAG_RECV); printf("相手が入力した値%dに対し、サーバーはこう返しました。\n",data_type.value2); switch(data_type.value){ case 's': printf("もっと小さい\n"); break; case 'l': printf("もっと大きい\n"); break; case 'c': printf("正解\n"); printf("あなたの負けです\n"); printf("ゲーム終了です\n"); set_send_flag(SEND_FLAG_EMPTY); break; default: break; } if(data_type.turnflag == 1){ set_send_flag(SEND_FLAG_EMPTY); if(data_type.value != 'c'){ printf("あなたのターンです。\n"); } } break; case 13: /* 終了通知 */ set_send_flag(SEND_FLAG_EMPTY); printf("終了しました\n"); break; case 15: /* 回線切断による終了 */ set_send_flag(SEND_FLAG_EMPTY); printf("相手の回線切断により終了しました\n"); break; break; case 19: /* 拒否応答 */ set_send_flag(SEND_FLAG_RECV); printf("コマンドは拒否されました\n"); if(data_type.turnflag == 1){ set_send_flag(SEND_FLAG_EMPTY); } break; default: break; } } #ifdef DEBUG printf("# receive thread end\n"); #endif /* スレッド生存フラグをOFFにする */ g_r_thread_alive = 0; return 0; } /* スレッド監視スレッド */ int comm_thread(void* pointer){ IPaddress client_ip; TCPsocket client_sock; SDL_Thread *s_thread; SDL_Thread *r_thread; g_comm_thread_alive = 1; /* 通信の準備はこのスレッド内で行う */ SDLNet_ResolveHost(&client_ip, REMOTE_HOST_IP, REMOTE_HOST_PORT); do { /* ソケットオープン */ client_sock = SDLNet_TCP_Open(&client_ip); if(client_sock != NULL){ printf("Open\n"); } else { printf("%s\n", SDLNet_GetError()); break; } /* ここに置くのはちょっと変かもしれないが・・・ */ g_s_thread_alive = 1; g_r_thread_alive = 1; /* 送受信スレッドの作成 */ s_thread = SDL_CreateThread(send_thread, client_sock); r_thread = SDL_CreateThread(recv_thread, client_sock); if(s_thread == NULL){ g_s_thread_alive = 0; } if(r_thread == NULL){ g_r_thread_alive = 0; } while(1){ /* スレッドが停止したら、もう片方のスレッドも停止させ、自身も終了に向かう */ if(g_r_thread_alive == 0){ #ifdef DEBUG printf("r gone\n"); #endif if(g_s_thread_alive == 1){ SDL_KillThread(s_thread); } break; } if(g_s_thread_alive == 0){ #ifdef DEBUG printf("s gone\n"); #endif if(g_r_thread_alive == 1){ SDL_KillThread(r_thread); } break; } /* タイムアウトチェック */ if(check_timeout(TIMEOUT) != 0){ /* タイムアウト とりあえず今回はスレッドを終了させる */ printf("timeout\n"); if(g_s_thread_alive == 1){ SDL_KillThread(s_thread); } if(g_r_thread_alive == 1){ SDL_KillThread(r_thread); } break; } /* メインスレッドからの停止依頼 */ if(g_finished_flag == 1){ #ifdef DEBUG printf("quit\n"); #endif if(g_s_thread_alive == 1){ SDL_KillThread(s_thread); } if(g_r_thread_alive == 1){ SDL_KillThread(r_thread); } break; } SDL_Delay(20); } /* ソケットクローズ */ SDLNet_TCP_Close(client_sock); } while(0); /* 生存フラグOFF */ g_comm_thread_alive = 0; return 0; } /* 送信データにセットする処理 */ int set_send_data(char* data, int len){ int ret_value = 0; SDL_mutexP(g_send_data_mutex); do { if(len < 1 || 1024 < len){ ret_value = -1; break; } /* フラグチェック */ if(g_send_data.flag == SEND_FLAG_EMPTY){ /* SEND_FLAG_EMPTYであればセットする */ memcpy(g_send_data.send_data, data, len); g_send_data.flag = SEND_FLAG_SET; g_send_data.len = len; } else { ret_value = g_send_data.flag; break; } ret_value = 0; } while(0); SDL_mutexV(g_send_data_mutex); return ret_value; } /* 送信フラグの初期化 */ int init_send_data(){ SDL_mutexP(g_send_data_mutex); g_send_data.flag = SEND_FLAG_EMPTY; SDL_mutexV(g_send_data_mutex); return 0; } /* 送信データの取得 */ int copy_send_data(t_send_data* dst){ SDL_mutexP(g_send_data_mutex); memcpy(dst, &g_send_data, sizeof(t_send_data)); SDL_mutexV(g_send_data_mutex); return 0; } /* 送信フラグのセット */ int set_send_flag(int flag){ SDL_mutexP(g_send_data_mutex); g_send_data.flag = flag; SDL_mutexV(g_send_data_mutex); return 0; } /* 送信時間のセット */ int set_send_time(unsigned int time){ SDL_mutexP(g_send_data_mutex); g_send_data.time = time; SDL_mutexV(g_send_data_mutex); return 0; } /* タイムアウトチェック */ int check_timeout(int timeout){ unsigned int time; unsigned int tick; int diff; int flag; SDL_mutexP(g_send_data_mutex); flag = g_send_data.flag; if(flag == SEND_FLAG_SENT){ time = g_send_data.time; } tick = SDL_GetTicks(); SDL_mutexV(g_send_data_mutex); /* フラグチェック */ if(flag != SEND_FLAG_SENT){ /* SEND_FLAG_SENT(送信済み)でなければ抜ける */ return 0; } /* タイムアウトの計算 */ diff = tick - time; if(diff < 0){ diff = time - 0xFFFFFFFF + tick + 1; } if(diff >= timeout){ return -1; } return 0; } /* コンソール入出力スレッド */ int input_thread(void* pointer){ char buffer[10]; t_data_type data_type; int ret_value; int value; while(1){ fgets(buffer, sizeof(buffer), stdin); /* Eが入力されたら終了 */ if(buffer[0] == 'E'){ break; } value = atoi(buffer); #ifdef DEBUG printf("# value = %d\n",value); #endif if(value != 0){ if(value < 1){ printf("小さすぎます。\n"); } else if(value > 100){ printf("大きすぎます。\n"); } data_type.value = value; data_type.type = 2; } else { #ifdef DEBUG printf("# send command :key = %c\n",buffer[0]); #endif switch(buffer[0]){ case 's': /* ゲームスタート依頼 */ data_type.type = 1; data_type.value = 0; break; case 'c': /* ゲーム終了依頼 */ data_type.type = 3; data_type.value = 0; break; default: data_type.type = 0; break; } } if(data_type.type == 0){ continue; } /* 送信データにセット */ ret_value = set_send_data((char*)&data_type, sizeof(data_type)); if(ret_value != 0){ printf("今は入力できません(あなたの番ではありません)\n"); } } g_input_thread_alive = 0; return 0; } /* メイン関数 */ int main(int argc, char* argv[]){ SDL_Thread *c_thread; SDL_Thread *i_thread; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); /* ミューテックスの作成 */ g_send_data_mutex = SDL_CreateMutex(); /* 送信フラグの初期化 */ init_send_data(); /* スレッド監視スレッドの作成 */ g_comm_thread_alive = 1; c_thread = SDL_CreateThread(comm_thread, NULL); /* コンソール入出力用スレッドの作成 */ g_input_thread_alive = 1; i_thread = SDL_CreateThread(input_thread, NULL); do { if(c_thread == NULL){ break; } if(i_thread == NULL){ break; } while(1) { if(g_comm_thread_alive == 0){ /* 監視スレッドが終了していたら終了 */ if(g_input_thread_alive == 1){ SDL_KillThread(i_thread); } break; } if(g_input_thread_alive == 0){ if(g_comm_thread_alive == 1){ g_finished_flag = 1; SDL_WaitThread(c_thread, NULL); } } SDL_Delay(20); } } while(0); /* ミューテックス破棄 */ SDL_DestroyMutex(g_send_data_mutex); printf("終了しました。\n"); SDLNet_Quit(); SDL_Quit(); return 0; }
次にサーバープログラムです。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #include <time.h> #define SERVER_HOST 5555 #define MAX_CONNECTION 2 #define ANSWER_NUMBER 54 #define SEND_FLAG_EMPTY 0 #define SEND_FLAG_SET 1 #define SEND_FLAG_SENT 2 #define SEND_FLAG_RECV 3 #define RECV_FLAG_DONE 0 #define RECV_FLAG_RECV 1 #define RECV_FLAG_READY 2 #define ZERO_MEMORY(x, y) memset(x, 0, y) int g_answer = 1; int g_turn = -1; /* どのコネクションの順番か */ typedef struct { int flag; char data[1024]; int len; unsigned int time; } t_rs_data; typedef struct { char type; char value; char value2; char turnflag; } t_data_type; /* 通信統括スレッドで送受信スレッドを管理するための構造体 */ typedef struct { int connection_no; TCPsocket sock; int send_thread_alive; int recv_thread_alive; int thread_fin; } thread_arg; /* メインスレッドで通信統括スレッドを管理するための構造体 */ typedef struct { SDL_Thread *thread; thread_arg arg; } thread_set; int set_send_data(void*, int, int); int set_all_send_data(void*, int, int); int g_fin_flag = 0; t_rs_data g_recv_data[MAX_CONNECTION]; t_rs_data g_send_data[MAX_CONNECTION]; thread_set g_t_set[MAX_CONNECTION]; int send_thread(void* pointer){ TCPsocket accept_sock; thread_arg *arg; int result; int con_no; arg = (thread_arg*)pointer; accept_sock = arg->sock; con_no = arg->connection_no; g_send_data[con_no].flag = SEND_FLAG_EMPTY; while(1){ while(g_send_data[con_no].flag != SEND_FLAG_SET){ SDL_Delay(20); } result = SDLNet_TCP_Send(accept_sock, g_send_data[con_no].data, g_send_data[con_no].len); if(result < g_send_data[con_no].len){ printf("send error\n"); break; } printf("#S to %d\n",con_no); g_send_data[con_no].flag = SEND_FLAG_SENT; } arg->send_thread_alive = 0; return 0; } int set_all_send_data(void* data, int len, int except){ int i; for(i = 0; i < MAX_CONNECTION ; i++){ if(i != except){ set_send_data(data, len, i); } } return 0; } int set_send_data(void* data, int len, int con_no){ if(g_t_set[con_no].arg.connection_no != -1){ memcpy(g_send_data[con_no].data, data, len); g_send_data[con_no].flag = SEND_FLAG_SET; g_send_data[con_no].len = len; return 0; } return -1; } /* 拒否応答をセットする処理 */ int set_refuse_data(int con_no, int turnflag){ t_data_type data_type; data_type.type = 19; data_type.turnflag = turnflag; return set_send_data(&data_type, sizeof(data_type), con_no); } int recv_thread(void* pointer){ TCPsocket accept_sock; thread_arg *arg; int con_no; arg = (thread_arg*)pointer; accept_sock = arg->sock; con_no = arg->connection_no; int result; g_recv_data[con_no].flag = RECV_FLAG_DONE; while(1){ while(g_recv_data[con_no].flag != RECV_FLAG_DONE){ SDL_Delay(20); } result = SDLNet_TCP_Recv(accept_sock, g_recv_data[con_no].data, sizeof(g_recv_data[con_no].data) - 1); if(result <= 0){ printf("Host%d: DISCONNECT\n",arg->connection_no); break; } printf("#R from %d\n", con_no); g_recv_data[con_no].data[result] = 0x00; g_recv_data[con_no].flag = RECV_FLAG_READY; } arg->recv_thread_alive = 0; return 0; } /* アクションメイン */ int action_main(){ int i, j, k; t_data_type r_data_type; t_data_type s_data_type; /* 受信データの確認 */ for(i = 0; i < MAX_CONNECTION; i++){ if(g_t_set[i].arg.connection_no != -1 && g_recv_data[i].flag == RECV_FLAG_READY){ if(i == g_turn || g_turn == -1){ /* 受信許可 */ ZERO_MEMORY(&s_data_type, sizeof(s_data_type)); memcpy(&r_data_type, g_recv_data[i].data, sizeof(r_data_type)); printf("#A %d : t %d v %d\n", i, r_data_type.type, r_data_type.value); switch(r_data_type.type){ case 1: /* ゲーム開始依頼 */ if(g_turn != -1){ /* ゲーム開始前でなければ、拒否応答 */ set_refuse_data(i, 0); } else { /* メンバーが揃っていなかったら拒否 */ k = 0; for(j = 0; j < MAX_CONNECTION; j++){ if(g_t_set[j].arg.connection_no != -1){ k++; } } if(k != MAX_CONNECTION){ set_refuse_data(i, 1); break; } /* ゲーム開始前であれば、ゲームを開始する */ g_turn = rand() % MAX_CONNECTION; g_answer = rand() % 100 + 1; /* 開始通知の送信 */ s_data_type.type = 11; s_data_type.turnflag = 1; set_send_data(&s_data_type, sizeof(s_data_type), g_turn); s_data_type.turnflag = 0; set_all_send_data(&s_data_type, sizeof(s_data_type), g_turn); } break; case 2: /* 値入力 */ if(g_turn == -1){ set_refuse_data(i, 1); } else { if(r_data_type.value < g_answer){ s_data_type.value = 'l'; } else if(r_data_type.value > g_answer){ s_data_type.value = 's'; } else { s_data_type.value = 'c'; g_turn = -1; } s_data_type.value2 = r_data_type.value; s_data_type.type = 12; set_send_data(&s_data_type, sizeof(s_data_type), i); s_data_type.type = 14; if(g_turn != -1){ g_turn++; if(g_turn >= MAX_CONNECTION){ g_turn = 0; } } for(j = 0; j < MAX_CONNECTION; j++){ if(j != i){ if(g_turn != j && g_turn != -1){ s_data_type.turnflag = 0; } else { s_data_type.turnflag = 1; } set_send_data(&s_data_type, sizeof(s_data_type), j); } } } break; case 3: /* 終了依頼 */ if(g_turn == -1){ set_refuse_data(i, 1); break; } s_data_type.type = 13; /* 全員に終了通知 */ set_all_send_data(&s_data_type, sizeof(s_data_type), -1); g_turn = -1; break; default: break; } } else { /* 受信拒否 */ set_refuse_data(i, 0); } g_recv_data[i].flag = RECV_FLAG_DONE; } } return 0; } /* ゲーム強制終了 */ int send_game_terminate(){ int i; t_data_type data_type; data_type.type = 15; for(i = 0; i < MAX_CONNECTION; i++){ if(g_t_set[i].arg.connection_no != -1){ set_send_data(&data_type, sizeof(data_type), i); } } return 0; } int thread_func(void* pointer){ thread_arg *arg; TCPsocket accept_sock; SDL_Thread *s_thread; SDL_Thread *r_thread; arg = (thread_arg*)pointer; accept_sock = arg->sock; arg->send_thread_alive = 1; arg->recv_thread_alive = 1; s_thread = SDL_CreateThread(send_thread, pointer); r_thread = SDL_CreateThread(recv_thread, pointer); while(1){ if(arg->send_thread_alive == 0){ printf("## Con %d : send thread has gone. start closing \n", arg->connection_no); if(arg->recv_thread_alive != 0){ SDL_KillThread(r_thread); arg->recv_thread_alive = 0; break; } } if(arg->recv_thread_alive == 0){ printf("## Con %d : receive thread has gone. start closing \n", arg->connection_no); if(arg->send_thread_alive != 0){ SDL_KillThread(s_thread); arg->send_thread_alive = 0; break; } } if(arg->thread_fin == 1){ printf("## Con %d : closing\n", arg->connection_no); if(arg->recv_thread_alive != 0){ SDL_KillThread(r_thread); arg->recv_thread_alive = 0; break; } if(arg->send_thread_alive != 0){ SDL_KillThread(s_thread); arg->send_thread_alive = 0; break; } break; } SDL_Delay(20); } SDLNet_TCP_Close(accept_sock); printf("## Con %d : closed\n", arg->connection_no); arg->connection_no = -1; if(g_turn != -1){ g_turn = -1; /* ゲーム強制終了を全ユーザーに送信する */ send_game_terminate(); } return 0; } int net_main(void* pointer){ IPaddress server_ip; TCPsocket server_sock; int i; for(i = 0; i < MAX_CONNECTION; i++){ g_t_set[i].arg.connection_no = -1; } SDLNet_ResolveHost(&server_ip, NULL, SERVER_HOST); server_sock = SDLNet_TCP_Open(&server_ip); do { if(server_sock != NULL){ printf("OPEN\n"); } else { printf("%s\n", SDLNet_GetError()); break; } while(1){ for(i = 0; i < MAX_CONNECTION; i++){ if(g_t_set[i].arg.connection_no == -1 && g_turn == -1){ g_t_set[i].arg.sock = SDLNet_TCP_Accept(server_sock); if(g_t_set[i].arg.sock != NULL){ printf("# ACCEPT Host %d\n", i); g_t_set[i].arg.connection_no = i; g_t_set[i].arg.thread_fin = 0; g_t_set[i].thread = SDL_CreateThread(thread_func, &g_t_set[i].arg); } /* else { SDL_Delay(100); i--; } */ } } /* 受信後処理(もしあれば) */ action_main(); if(g_fin_flag == 1){ printf("# start quitting\n"); break; } SDL_Delay(100); } for(i = 0; i < MAX_CONNECTION; i++){ if(g_t_set[i].arg.connection_no != -1){ printf("# connection no %d : start closing\n", i); g_t_set[i].arg.thread_fin = 1; SDL_WaitThread(g_t_set[i].thread, NULL); printf("# connection no %d : closed\n", i); } else { printf("# connection no %d : no connection\n", i); } } SDLNet_TCP_Close(server_sock); } while(0); return 0; } int main(int argc, char* argv[]){ char command[100]; SDL_Thread *thread; int exitflag = 0; srand(time(NULL)); SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); thread = SDL_CreateThread(net_main, NULL); g_fin_flag = 0; while(exitflag == 0){ fgets(command, sizeof(command), stdin); switch(command[0]){ case 'E': g_fin_flag = 1; SDL_WaitThread(thread, NULL); exitflag = 1; break; } } SDLNet_Quit(); SDL_Quit(); return 0; }
- 遊び方:
- クライアントキーコマンド
- s : ゲームスタート。ゲームが既に始まっている/人数が揃っていないと拒否されます。
- 数字 : 自分の考えた数字をサーバーに送信します。ゲームが始まっていない/自分の番でないと拒否されます。
- c : 終了します。ゲームが始まっていない/自分の番でないと拒否されます。
- E : プログラムを終了します。ゲーム中であれば、ゲームを終了します。
- サーバーキーコマンド
- E : プログラムを終了します。ゲーム中であれば、ゲームを終了します。
- クライアントキーコマンド
以下、内部情報
- 通信コマンド(1バイト目:type、2バイト目:value、3バイト目:value2、4バイト目:turnflag)
- 1 : ゲーム開始依頼
- 2 : 数字入力(value : 入力数字)
- 3 : ゲーム終了依頼
- 11 : ゲーム開始通知(turnflag : 入力許可(1: 許可、0: 不許可))
- 12 : 自分の入力結果(value : 結果(l,s,c))
- 13 : 相手の入力結果(value : 結果(l,s,c) , value2 : 相手の入力した数字 , turnflag : 入力許可(1: 許可、0: 不許可))
- 15 : 相手の回線切断によるゲーム終了
- 19 : 拒否応答(turnflag : 入力許可(1: 許可、0: 不許可))
ただの数当てゲームだけど結構複雑になってしまった。コマンド判定やターン制御とか結構いい加減になってしまいました。たかが数当てゲームなので許してちょうだい。
2~3回ぐらいしか遊んでいないので、バグは必ずあると思います。見つけたら勝手に直しちゃってください。
※メール、リンクは適当に。
Copyright (c) 2009 greencap