通信プログラムの試作ーーー UDP を用いたじゃんけんゲームシステム ーーーー裘彬濱 南山大学情報理工学部 ソフトウェア工学科青山研究室
1:UDP を用いたじゃんけんゲームシステムの概要 本システムは通信プロトコル UDP を用いた簡単なじゃんけんゲームシステムであり 単一のユーザ ( クライアント ) が参加し パソコン ( サーバ ) とじゃんけんゲームするシステムである 本システムはユーザがゲームに参加できる時間を制限しており 制限時間にオーバーすると サーバが止まるようになっている 本システムは優勝条件を定めており 優勝条件を満たした側に優勝者になり ゲームを終了させる 2: 応用プロトコルの説明 ( 通信順 ) C S ----- 開始 ------> (UDP の開始メッセージ ) <-ゲーム参加要請 -- --- 参加 / 不参加 --> ( じゃんけんの手 推定した数字 ) <---- 応答 ----- ( 結果 ) ( 以後くりかえし ) <---- 終了 ----- ( 優勝 / 制限時間オーバーしたら UDP の終了メッセージ ) time V V 応用プロトコルの説明 ( メッセージ形式 ) メッセージは簡単な文字列とする 区切りに CRLF を使い この単位で送受信する <1> クライアントからサーバ ( コマンド / 要求 ) 開始メッセージ CRLF ー 最初にクライアントからの開始メッセージが1 文字でなくてもいいが メッセージの内容が重要ではないので 1 文字でデータにした 参加 / 不参加 CRLF ー 参加は y 不参加は n
手 / 終了命令 CRLF ー 手はグー (r) チョキ (s) パー (p) で ゲーム終了は q で 処理が簡単になる <2> サーバからクライアント ( 応答 ) OK SP 説明文字列 CRLF NG SP 説明文字列 CRLF 説明文字列は簡潔なものにする 3: プログラム処理の流れの説明クライアントサーバ ---------------------------------------------------------------- sock = socket() ssock= socket() bind(ssock,,) -- 受付ポート設定ループ始め一回目 send(sock) -------------------> recv(sock,,) recv(sock) <------------------ send(sock,,) 二回目以降 ループ始め send(sock) -------------------> recv(sock,,) recv(sock) <------------------ send(sock,,) -------------------> --- ( 制限時間 <------------------ ( 制限時間オーバー )------- オーバー通知 ) ::: -------------------> 結果通知 <------------------ send(sock,,)( 優勝 ) ループ終わり ループ終わり 終了 終了 4: プログラムの実現 <1> 優勝の判断 サーバ側 int champion=3; ::: wins++; if(wins==champion){ sendto(sock,"-you lost!game over",20, 0,(const struct sockaddr *) &remote,
remotelen);return 0; else if ((handc==0 &&hands==2) (handc==1 &&hands==0) (handc==2 &&hands==1)){ winc++; if(winc==champion){ sendto(sock,"+you're the CHAMPION",20, 0,(const struct sockaddr *) &remote, remotelen);return 0; クライアント側 if(recvbuf[0]=='+' recvbuf[0]=='-')return 0; /******************************************************************* もしサーバが優勝したら クライアントに -You lost!game over というメッ セージを送る メッセージの頭文字 ー と + はクライアント側にゲーム結 果の符号である *******************************************************************/ <2> 制限時間をオーバーした場合の処理 struct fd_set fds,readfds; int n; struct timeval tv; ::: while(1){ ::: FD_ZERO(&readfds); FD_SET(sock,&readfds); tv.tv_sec=10; tv.tv_usec=0; memcpy(&fds,&readfds,sizeof(fd_set)); n=select(sock+1,&fds,null,null,&tv); if(n==0){ sendto(sock,"-time over!!",20, 0,(const struct sockaddr *) &remote, remotelen); printf("time over\n"); return 0; :::
5: テスト <1> テスト項目 ---1: ゲームに参加 / 不参加の返事に対するサーバ側の処理ー y/n を入力された場合と他のアルファベットを入力された場合に対するサーバ側の処理を確認する ---2: 制限時間をオーバーした場合に対するサーバ側の処理ー クライアントは制限時間内に返事しない時に サーバが正しく止まるかを確認する ---3: 制限時間オーバーによりサーバが止まった後 クライアントは送信した場合に対するクライアント側の処理ー サーバからの終了メッセージを正しく受信したか クライアントが正しく処理したかを確認する ---4: 優勝に対する処理ー 優勝の条件を満たした場合に対する処理を確認する ---5: クライアントからの手に対する処理ー クライアントから r/p/s または q を受けた場合とそれら以外の場合の処理 テスト結果 ---1: ゲームに参加 / 不参加の返事に対するサーバ側の処理結果 クライアントの入力サーバの応答テスト結果 y/n Input r/p/s or q. w Erro,input y / n! ---2: 制限時間をオーバーした場合に対するサーバ側の処理制限時間を過ぎると サーバが time over を出力し 止まった しかし この時点で クライアントに終了メッセージを正しく送ったかは確認できない 3 確認結果に移る ---3: 制限時間オーバーによりサーバが止まった後 クライアントは送信した場合に対するクライアント側の処理サーバが止まった後 クライアントは送信し 送信前に受け取ったサーバの終了メッセージ -Time over!! を表示し クライアントプログラムが終了したことを確認した
---4: 優勝に対する処理 サーバクライアントテスト結果 サーバが優勝した 止まった サーバから受信 -You lost!game over クライアントが優勝 した 止まった サーバから受信 +You're the CHAMPION ---5: クライアントからの手に対する処理 クライアントの手サーバの応答テスト結果 r/p/s 正しく判断 q -Game over を送信した後 止まった w Erro,input again! 6: 考察と感想サーバとクライアントとのデータの送受信については どうも二回連続送受信ができない ( と思っている ) 送信した後は受信を待ちである また 送信するデータの長さが長くなると 一部しか送信 ( 受信 ) できない この点については 自分が間違っていると思っているが サーバからの sock にデータの長さを書いても クライアント側が一部しか表示できなかった どこが門題になるのがどうしても見つからず 結局やり方を変えて 簡潔なメッセージを送受信することにした 7: 参考文献 UDP を聞ってみよう (2) URL ーー http://x68000.q-e-d.net/~68user/net/udp-2.html select を聞う ( タイムアウト付き ) URL ーー http://www.geekpage.jp/programming/winsock/select-with-timeout.php
クライアントプログラム #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/socket.h> // inet_addr #include <netinet/in.h> // inet_addr #include <arpa/inet.h> // inet_addr #include <string.h> // strncpy int main(){ struct sockaddr_in remote; char sendbuf[10], recvbuf[20]; int sock, ret; sock = socket(pf_inet,sock_dgram,0); remote.sin_family = AF_INET; remote.sin_port = htons(60000); remote.sin_addr.s_addr = inet_addr("127.0.0.1"); strncpy(sendbuf,"1",1); while(1){ ret = sendto(sock, sendbuf,6, 0, (const struct sockaddr *) &remote, sizeof(remote)); ret = recvfrom(sock, recvbuf, sizeof(recvbuf)+20, 0, NULL, NULL); if (ret < 0) return -1; recvbuf[ret] = '\0'; printf("%s\n", recvbuf); if(recvbuf[0]=='+' recvbuf[0]=='-')return 0; fgets(sendbuf,sizeof(sendbuf),stdin); return 0;
サーバプログラム #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> int main(){ struct sockaddr_in local, remote; char recvbuf[1500],join[1024]="join the game?(y/n)"; int sock,champion=3,winc=0,wins=0; socklen_t remotelen = sizeof(remote); sock = socket(pf_inet,sock_dgram,0); local.sin_family = AF_INET; local.sin_port = htons(60000); local.sin_addr.s_addr = htonl(inaddr_any); if (bind(sock, (struct sockaddr *) &local, sizeof(local)) < 0) {printf("error\n"); return -1; recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *) &remote, &remotelen); // remotelen sendto(sock,join,strlen(join)+1, 0, (const struct sockaddr *) &remote, remotelen); while(1){ // 返事待ち recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *) &remote, &remotelen); // remotelen if(recvbuf[0]=='n')break; else if(recvbuf[0]=='y'){ sendto(sock,"input r/p/s or q.",20, 0,(const struct sockaddr *) &remote, remotelen); while(1){ int handc,hands=(int) (3.0*random()/(RAND_MAX+1.0));// サーバの手 recvfrom(sock, recvbuf, sizeof(recvbuf), 0,
(struct sockaddr *) &remote, &remotelen); // remotelen // クライアントの手の決定 if (recvbuf[0] == 'q') break; else if (recvbuf[0] == 'r') handc = 0; else if (recvbuf[0] == 'p') handc = 1; else if (recvbuf[0] == 's') handc = 2; else { sendto(sock,"erro,input again!",20,0, (const struct sockaddr *) &remote, remotelen); continue; if((handc==0 &&hands==1) (handc==1 &&hands==2) (handc==2 &&hands==0)){ wins++; if(wins==champion){ sendto(sock,"-you lost!game over",20, 0,(const struct sockaddr *) &remote, remotelen);return 0; sendto(sock,"you lost!try again",20, 0,(const struct sockaddr *) &remote, remotelen); else if ((handc==0 &&hands==2) (handc==1 &&hands==0) (handc==2 &&hands==1)){ winc++; if(winc==champion){ sendto(sock,"+you're the CHAMPION",20, 0,(const struct sockaddr *) &remote, remotelen);return 0; sendto(sock,"you won!next game",20, 0,(const struct sockaddr *) &remote, remotelen); else if((handc==1&&hands==1) (handc==2&&hands==2) (handc==0&&hands==0)){ sendto(sock,"same,input again",20, 0,(const struct sockaddr *) &remote, remotelen); // y/n 以外の文字を入力された場合 else sendto(sock,"erro,input y / n!",20,0, (const struct sockaddr *) &remote, remotelen); return 0;