ホームに戻る
オンライン対戦ジャンケンゲームを作る
jyanken (2012/3/4 119kb/zip)
0、はじめに
オンラインで対戦できるジャンケンゲームを作ります。
サーバーはLinuxサーバーを使用。
サーバープログラムはC言語で作成。
クライアントはブラウザからFlashで実行する。
クライアントはActionScript3.0で作成。
TCP/IPでクラアントサーバ通信を考えます。
ストリームソケットを考えます。
また、ポート843で <policy-file-request/> に対応する
C言語のサーバーも書きました。
1、socket(サーバ、クライアント)
int srcSocket = socket(AF_INET, SOCK_STREAM, 0);
戻り値:エラーだと -1 を返す。
2、bind(サーバ)
指定したポートがすでに使用されているとエラーになる。
struct sockaddr_in srcAddr;
memset(&srcAddr, 0, sizeof(srcAddr));
srcAddr.sin_port = htons(port);
srcAddr.sin_family = AF_INET;
srcAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(srcSocket, (struct sockaddr *)&srcAddr, sizeof(srcAddr));
戻り値:正常 0 エラー -1
3、listen(サーバ)
第2引数は接続の受け付けを5つまで許可。
この数値は環境によって意味が異なるので注意。
接続できるクライアントの数を5に制限するわけではない。
listen(srcSocket, 5);
戻り値:正常 0 エラー -1
4、accept(サーバ)
接続の受け付け。
受け付けるまでブロックする。
struct sockaddr_in dstAddr;
int dstAddrSize = sizeof(dstAddr);
int dstSocket = accept(srcSocket, (struct sockaddr *)&dstAddr, &dstAddrSize);
戻り値:エラーだと -1 を返す。
5、connect(クライアント)
クライアントがサーバーに接続。
struct sockaddr_in dstAddr;
int dstAddrSize = sizeof(dstAddr);
memset(&dstAddr, 0, sizeof(dstAddr));
dstAddr.sin_family = AF_INET;
dstAddr.sin_port = htons(port);
dstAddr.sin_addr.S_un.S_addr = inet_addr(ip);
int dstSocket = connect(srcSocket, (struct sockaddr *)&dstAddr,
&dstAddrSize);
戻り値:正常 0 エラー -1
6、recv(サーバ、クライアント)
BUFFER_SIZE のサイズだけデータを受け取る。
buffer は書き込む位置のポインタ。
データを受け取れるまでブロック。
1回ですべてのデータを受け取れるわけではない。
1回で受け取れない場合は数回に分けて受け取る必要がある。
data_size は受け取ったデータサイズである。
data_size から計算して残りのデータサイズを計算できる。
#define BUFFER_SIZE 256
int data_size = recv(dstSocket, buffer, BUFFER_SIZE, 0);
戻り値:接続が切れると 0 エラーだと -1 を返す。
7、send(サーバ、クライアント)
1回ですべてのデータを送信するわけではない。
data_size は送ったデータサイズである。
送信バッファがいっぱいになるとブロックする。
int data_size = send(dstSocket, "ok", 2, 0);
戻り値:エラーだと -1 を返す。
8、close(サーバ、クライアント)
接続を切る。
close(srcSocket);
戻り値:正常 0 エラー -1
10、サーバーの流れ
以下のようになる。
1,socket
2,bind
3,listen
4,accept
5,recv
6,send
ジャンケンは2人対戦である。
よって、3人目の accept があった場合は強制的に close する。
また、対戦した2人は強制的に close はしない。
対戦する2人はスレッド内で recv と send を繰り返す。
クライアント側が切断した場合にサーバーは recv で 0 を受け取る。
recv で 0 を受け取ったら、スレッドのループを抜ける。
対戦しているクライアントが抜けた場合に、
新たなクライアントを accept できるようにする。
/*
* ジャンケンゲーム用サーバープログラム
* gcc serv.c -lpthread
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT 40002
#define BUFFER_SIZE 256
typedef struct{
int *sock;
int num;
int *te;
int *opposite_te;
}player;
static pthread_mutex_t mutex;
static void* thread(void* pParam);
int main(){
int result;
int te[2];
player p[2];
unsigned short port = PORT;
int srcSocket;
int dstSocket[2];
struct sockaddr_in srcAddr;
struct sockaddr_in dstAddr;
int dstAddrSize = sizeof(dstAddr);
pthread_t tid[2];
te[0] = 0;
te[1] = 0;
dstSocket[0] = -1;
dstSocket[1] = -1;
memset(&srcAddr, 0, sizeof(srcAddr));
srcAddr.sin_port = htons(port);
srcAddr.sin_family = AF_INET;
srcAddr.sin_addr.s_addr = htonl(INADDR_ANY);
/* socket */
srcSocket = socket(AF_INET, SOCK_STREAM, 0);
if(srcSocket == -1){
printf("socket error\n");
return -1;
}
/* bind */
result = bind(srcSocket, (struct sockaddr *) &srcAddr, sizeof(srcAddr));
if(result == -1){
printf("bind error\n");
return -1;
}
/* listen */
result = listen(srcSocket, 5);
if(result == -1){
printf("listen error\n");
return -1;
}
pthread_mutex_init(&mutex, NULL);
while(1){
int i;
int dstSock;
/* accept */
dstSock = accept(srcSocket, (struct sockaddr *)&dstAddr, &dstAddrSize);
if(dstSock == -1){
continue;
}
for(i = 0; i < 3; i++){
if(i == 2){
printf("login is denied\n");
close(dstSock);
break;
}
else if(dstSocket[i] == -1){
printf("player%d:login\n", (i + 1));
dstSocket[i] = dstSock;
p[i].sock = &dstSocket[i];
p[i].num = (i + 1);
p[i].te = &te[i];
p[i].opposite_te = &te[(i + 1) % 2];
pthread_create(&tid[i], NULL, thread, &p[i]);
break;
}
}
}
pthread_mutex_destroy(&mutex);
}
/* thread */
void* thread(void* pParam)
{
int numrcv;
char buffer[BUFFER_SIZE + 10];
player *p = (player *)pParam;
int *dstSocket = p->sock;
int num = p->num;
int *te = p->te;
int *opposite_te = p->opposite_te;
while(1){
numrcv = recv(*dstSocket, buffer, BUFFER_SIZE, 0);
if(numrcv == 0 || numrcv == -1) {
goto close;
}
buffer[numrcv] = '\0';
printf("received%d: %s\n", num, buffer);
if(buffer[0] == 'g'){
*te = 1;
}
else if(buffer[0] == 'c'){
*te = 2;
}
else if(buffer[0] == 'p'){
*te = 3;
}
else{
continue;
}
while(1){
numrcv = recv(*dstSocket, buffer, BUFFER_SIZE, 0);
if(numrcv == 0 || numrcv == -1) {
goto close;
}
buffer[numrcv] = '\0';
printf("received%d: %s\n", num, buffer);
if(*opposite_te != 0){
char buf[10];
pthread_mutex_lock(&mutex);
if(*opposite_te == 1){
buf[0] = 'g';
buf[1] = '\0';
send(*dstSocket, buf, 2, 0);
}
else if(*opposite_te == 2){
buf[0] = 'c';
buf[1] = '\0';
send(*dstSocket, buf, 2, 0);
}
else if(*opposite_te == 3){
buf[0] = 'p';
buf[1] = '\0';
send(*dstSocket, buf, 2, 0);
}
printf("send%d: %s\n", num, buf);
*opposite_te = 0;
pthread_mutex_unlock(&mutex);
break;
}
}
}
close:
*te = 0;
*dstSocket = -1;
printf("player%d:close\n", num);
return;
}
11、クライアントの流れ
以下のようになる。
1,socket
2,connect
3,send
4,イベントにて受信
クライアントはActionScript3.0で書く。
SocketではなくXMLSocketを使用。
AS3.0では send はメソッドで呼ぶ。
データの受信はイベントで受け取れる。
切断は close を呼ぶか画面を閉じると切断になる。
// Main.as
package {
import flash.display.Sprite;
import flash.events.*;
import flash.text.TextFormat;
import flash.text.TextField;
import flash.net.XMLSocket;
import flash.utils.*;
import flash.system.Security;
import flash.errors.IOError;
[SWF(width="300", height="200", backgroundColor="0xFFFFFF")]
public class Main extends Sprite {
private var socket:XMLSocket;
private var timer:Timer;
private var te1:Te1;
private var te2:Te2;
private var rect1:Rect1;
private var text_field:TextField;
private var ite1:int;
private var ite2:int;
private var state:int;
private var list:Array = [[0, 1, 2],[2, 0, 1],[1, 2, 0]];
public function Main():void {
state = 0;
text_field = new TextField();
var format:TextFormat = new TextFormat();
format.size = 20;
text_field.defaultTextFormat = format;
text_field.x = 175;
text_field.y = 25;
text_field.width = 100;
text_field.height = 50;
addChild(text_field);
te1 = new Te1();
te1.x = 50;
te1.y = 100;
addChild(te1);
te2 = new Te2();
te2.x = 50;
te2.y = 0;
addChild(te2);
rect1 = new Rect1(this, 175, 125, 100, 50);
rect1.setButtonText("log in");
addChild(rect1);
Security.loadPolicyFile("xmlsocket://127.0.0.1:843");
socket = new XMLSocket();
socket.addEventListener(Event.CLOSE, closeHandler);
socket.addEventListener(Event.CONNECT, connectHandler);
socket.addEventListener(DataEvent.DATA, dataHandler);
socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
socket.addEventListener(ProgressEvent.PROGRESS, progressHandler);
socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
timer = new Timer(3000, 0);
timer.addEventListener(TimerEvent.TIMER, onTick);
timer.start();
}
public function push():void {
var sendText:String;
if(state == 0){
socket.connect("127.0.0.1", 40002);
}
else if(state == 1) {
state = 2;
text_field.text = "waiting...";
te2.set_te2(3);
wait(20);
if(te1.get_te1() == 0) {
ite1 = 0;
sendText = "g";
}
else if(te1.get_te1() == 1) {
ite1 = 1;
sendText = "c";
}
else if (te1.get_te1() == 2) {
ite1 = 2;
sendText = "p";
}
try{
socket.send(sendText);
}
catch(e:IOError) {
text_field.text = "error!";
}
}
}
public function wait(count:uint ):void{
var start:uint = getTimer();
while(getTimer() - start < count){}
}
private function closeHandler(event:Event):void {
trace("closeHandler: " + event);
}
private function connectHandler(event:Event):void {
text_field.text = "log in ok!";
state = 1;
rect1.setButtonText("pong");
trace("connectHandler: " + event);
}
private function dataHandler(event:DataEvent):void {
trace("dataHandler: " + event);
if(state == 2) {
var o:oto = new oto();
o.play();
wait(1000);
var str:String = event.data;
if(str.charAt(0) == 'g') {
ite2 = 0;
te2.set_te2(0);
}
else if(str.charAt(0) == 'c') {
ite2 = 1;
te2.set_te2(1);
}
else if(str.charAt(0) == 'p') {
ite2 = 2;
te2.set_te2(2);
}
else{
te2.set_te2(3);
}
if(list[ite1][ite2] == 0) {
text_field.text = " draw.";
}
else if (list[ite1][ite2] == 1) {
text_field.text = " you win!";
}
else if (list[ite1][ite2] == 2) {
text_field.text = " you lose.";
}
state = 1;
}
}
private function ioErrorHandler(event:IOErrorEvent):void {
text_field.text = "log in error.";
trace("ioErrorHandler: " + event);
}
private function progressHandler(event:ProgressEvent):void {
trace("progressHandler loaded:" + event.bytesLoaded + " total: " + event.bytesTotal);
}
private function securityErrorHandler(event:SecurityErrorEvent):void {
trace("securityErrorHandler: " + event);
}
private function onTick(evt:TimerEvent):void {
var sendText:String;
if(state != 0) {
sendText = "t";
socket.send(sendText);
}
}
}
}
// Te1.as
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Graphics;
public class Te1 extends Sprite {
private var now_te:int;
private var bitmap:Array;
[Embed(source = 'gu.png')]
private var Image0:Class;
[Embed(source = 'choki.png')]
private var Image1:Class;
[Embed(source = 'pa.png')]
private var Image2:Class;
public function Te1() {
now_te = 0;
bitmap = new Array();
bitmap[0] = new Image0();
bitmap[1] = new Image1();
bitmap[2] = new Image2();
bitmap[now_te % 3].visible = true;
bitmap[(now_te + 1) % 3].visible = false;
bitmap[(now_te + 2) % 3].visible = false;
addChild(bitmap[0]);
addChild(bitmap[1]);
addChild(bitmap[2]);
this.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
public function get_te1():int{
return now_te % 3;
}
private function onMouseDown(event:MouseEvent):void {
now_te++;
bitmap[now_te % 3].visible = true;
bitmap[(now_te + 1) % 3].visible = false;
bitmap[(now_te + 2) % 3].visible = false;
}
}
}
// Te2.as
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Graphics;
public class Te2 extends Sprite {
private var now_te:int;
private var bitmap:Array;
[Embed(source = 'gu.png')]
private var Image0:Class;
[Embed(source = 'choki.png')]
private var Image1:Class;
[Embed(source = 'pa.png')]
private var Image2:Class;
[Embed(source = 'hatena.png')]
private var Image3:Class;
public function Te2() {
var i:int;
now_te = 3;
bitmap = new Array();
bitmap[0] = new Image0();
bitmap[1] = new Image1();
bitmap[2] = new Image2();
bitmap[3] = new Image3();
for (i = 0; i < 3; i++) {
bitmap[i].rotation = 180.0;
bitmap[i].x += 100.0;
bitmap[i].y += 100.0;
}
addChild(bitmap[0]);
addChild(bitmap[1]);
addChild(bitmap[2]);
addChild(bitmap[3]);
}
public function set_te2(te:int):void {
bitmap[te % 4].visible = true;
bitmap[(te + 1) % 4].visible = false;
bitmap[(te + 2) % 4].visible = false;
bitmap[(te + 3) % 4].visible = false;
}
}
}
// Rect1.as
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Graphics;
import flash.text.TextField;
import flash.text.TextFormat;
public class Rect1 extends Sprite {
private var px:int;
private var py:int;
private var pw:int;
private var ph:int;
private var g:Graphics;
private var text_field:TextField;
private var main:Main;
public function Rect1(main:Main, px:int = 0, py:int = 0, pw:int = 0, ph:int = 0) {
this.main = main;
this.px = px;
this.py = py;
this.pw = pw;
this.ph = ph;
g = this.graphics;
this.draw();
text_field = new TextField();
var format:TextFormat = new TextFormat();
format.size = 30;
text_field.defaultTextFormat = format;
text_field.selectable = false;
addChild(text_field);
this.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
this.addEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
this.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
public function setX(px:int):void { this.px = px;}
public function setY(py:int):void { this.py = py;}
public function getX():int { return this.px;}
public function getY():int { return this.py;}
public function setButtonText(str:String):void {
text_field.text = str;
text_field.x = this.px + (pw - text_field.textWidth) / 2;
text_field.y = this.py + (ph - text_field.textHeight) / 2;
text_field.width = 100;
text_field.height = 50;
}
public function draw(color:int = 0xFFFFFF):void {
g.lineStyle(2, 0x000000, 1.0);
g.beginFill(color, 1.0);
g.drawRoundRect(this.px, this.py, this.pw, this.ph, 20, 20);
g.endFill();
}
private function onMouseDown(event:MouseEvent):void {
this.draw(0x000000);
text_field.textColor = 0xFFFFFF;
}
private function onMouseOut(event:MouseEvent):void {
this.draw();
text_field.textColor = 0x000000;
}
private function onMouseUp(event:MouseEvent):void {
this.draw();
text_field.textColor = 0x000000;
main.push();
}
}
}
// oto.as
package
{
import flash.media.Sound;
public class oto {
private var sound:Array;
[Embed(source = 'jyanken.mp3')]
private var Sound0:Class;
public function oto() {
var i:int;
sound = new Array();
sound[0] = new Sound0();
}
public function play():void {
sound[0].play();
}
}
}
12、ソケットポリシーファイル
FlashをサーバーにTCPで接続する場合、
どのドメインのFlashでどのポートへの接続かを確認します。
2012年3月の時点でFlashは次のようなチェックを行います。
まず、843ポートに <policy-file-request/> を送る。
以下のような返答があれば
mniwa.web.fc2.com でダウンロードされたFlashを
12345 のポートにアクセスさせることを許可します。
(ワイルドカード * も有効です。)
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="mniwa.web.fc2.com" to-ports="12345"/>
</cross-domain-policy>
もし3秒たっても返答が無い場合は、
Flashから 12345 のポートに <policy-file-request/> を送って、
上の返答があれば通信を許可します。
この仕組みを満たさない場合は通信が行えません。
以下はC言語のサーバープログラムのサンプルです。
ポート843を使用するので root で起動してください。
/*
* Flash からの <policy-file-request/> に対して
* port 843 で返答するサーバープログラム
* 許可するドメインとポート番号は XML文を書き換えてください
*
* gcc pserv.c
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT 843
#define BUFFER_SIZE 256
int main(){
int result;
unsigned short port = PORT;
int srcSocket;
int dstSocket;
struct sockaddr_in srcAddr;
struct sockaddr_in dstAddr;
int dstAddrSize = sizeof(dstAddr);
memset(&srcAddr, 0, sizeof(srcAddr));
srcAddr.sin_port = htons(port);
srcAddr.sin_family = AF_INET;
srcAddr.sin_addr.s_addr = htonl(INADDR_ANY);
/* socket */
srcSocket = socket(AF_INET, SOCK_STREAM, 0);
if(srcSocket == -1){
printf("socket error\n");
return -1;
}
/* bind */
result = bind(srcSocket, (struct sockaddr *)&srcAddr, sizeof(srcAddr));
if(result == -1){
printf("bind error\n");
return -1;
}
/* listen */
result = listen(srcSocket, 1);
if(result == -1){
printf("listen error\n");
return -1;
}
while(1){
int numrcv;
char buffer[BUFFER_SIZE + 10];
/* accept */
dstSocket = accept(srcSocket, (struct sockaddr *)&dstAddr, &dstAddrSize);
if(dstSocket == -1){
printf("accept error\n");
continue;
}
/* recv */
numrcv = recv(dstSocket, buffer, BUFFER_SIZE, 0);
if(buffer[0] == '<' && buffer[1] == 'p'){
/* XML文 */
char *str_xml = "<?xml version=\"1.0\"?>\n<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n<cross-domain-policy>\n <allow-access-from domain=\"127.0.0.1\" to-ports=\"40002\"/>\n</cross-domain-policy>";
/* send */
send(dstSocket, str_xml, strlen(str_xml), 0);
printf("send str_xml\n");
}
else{
printf("error recieve:%s\n", buffer);
}
close(dstSocket);
}
}
13、Linuxコマンドの覚え書き
cd ディレクトリ移動
rm ファイルを削除
mkdir ディレクトリを作成
rm -r ディレクトリごと削除
ls ファイル一覧を見る
cp ファイルコピー
ps 起動プロセス確認
& 最後につけるとプロセスをバックグラウンドで起動
kill -HUP PID プロセスを停止
cdmod 755 file アクセス権を設定(r = 4, w = 2, x = 1)
find / -name httpd
yum -y install httpd
service httpd restart
chkconfig --level 35 httpd on
viコマンド
hjkl で上下左右移動
x で文字削除
a で追加入力モード
i で挿入入力モード
/ 前方検索
? 後方検索
Esc で入力モードから戻る
:q! で終了
:wq で書き込み終了
iptables の設定
iptables -L
iptables -P INPUT DROP
iptables -A INPUT -p tcp --dport 80 -j ACCEPT