過去ログ 9
過去ログは下にいくほど新しい記事となっております。
※注意:このサイトにあるプログラムのソースコードや、その他の情報はすべて無保証です。やばいと思ったら実行しないでください。コンパイル、実行などによって生じた損害に対する責任は負いませんので、ご了承ください。
古いノートパソコンをゲットしたので
6-12-2009
早速つないでみよう(リモートデスクトップ)
メモリ:256MB、CPU:Celeron 1600 という、ちょっと古いパソコン。
バリバリ動かすには力不足だけれども、実験用の機械としてはまあ使えるかな。
SDL_net
まあ、そんなわけで、通信の実験もできるので、↑こんなのやってみます。
パソコン1台でもできるけど、2台あった方が「本当に動いているぜ」というのが実感できます。
つなぐ
それではまずは接続から。
とりあえず、リモート側をサーバー、自分の方をクライアントと考えて、こちらからリモート側に接続しようとします。TCPを使います。楽だから。
クライアント側プログラムはこんな感じにします。192.168.0.3はサーバーのIPアドレス。ポート5555は開いておきます。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define REMOTE_HOST_IP "192.168.0.3" #define REMOTE_HOST_PORT 5555 int main(int argc, char* argv[]){ IPaddress client_ip; TCPsocket client_sock; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); SDLNet_ResolveHost(&client_ip, REMOTE_HOST_IP, REMOTE_HOST_PORT); client_sock = SDLNet_TCP_Open(&client_ip); if(client_sock != NULL){ printf("Open\n"); } else { printf("%s\n", SDLNet_GetError()); } SDLNet_Quit(); SDL_Quit(); return 0; }
SDLNet_Init()は初期化。そのままですね。
SDLNet_ResolveHost()は文字列で書かれたホスト名と数字で書かれたポート名からIPaddress構造体にセットする関数です。ホスト名はIPアドレスでOKなので、自分のところだったら、「127.0.0.1」でOKです。
このIPaddressはTCPsocketを作るのに使用します。
SDLNet_TCP_Open()で、つなぎにいこうとしています。NULLだったら失敗です。
実行すると、
$ ./a.out Couldn't connect to remote host
と、まあ、サーバー側が用意できていないのでエラーになります。
サーバー側プログラムは次のようにします。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define SERVER_HOST 5555 int main(int argc, char* argv[]){ IPaddress server_ip; TCPsocket server_sock; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); SDLNet_ResolveHost(&server_ip, NULL, SERVER_HOST); server_sock = SDLNet_TCP_Open(&server_ip); if(server_sock != NULL){ printf("OPEN\n"); } else { printf("%s", SDLNet_GetError()); return 1; } while(1){ SDL_Delay(100); } return 0; }
SDLNet_ResolveHost()の第2引数がNULLになっていますが、これはサーバーであることを意味します。つまり、受け側であることを宣言しているのです。
で、サーバー側プログラムを起動します。
無限ループで無限待ちになっているので、サーバー側プログラムで「OPEN」と表示されたら、クライアントプログラムを起動します。
$ ./a.out Open
今度はつなげました。
送信する/受信する
次に、送信と受信をしたいと思います。
まずは受信側です。受信側は、SDLNet_TCP_Recv()という関数で受信しますが、サーバーのソケットはそのまま使用できず、受け入れソケットを作る必要があります。とりあえず、受け入れソケットをつくるところまでいきます。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define SERVER_HOST 5555 int main(int argc, char* argv[]){ IPaddress server_ip; TCPsocket accept_sock; TCPsocket server_sock; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); 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; } accept_sock = SDLNet_TCP_Accept(server_sock); if(accept_sock != NULL){ printf("ACCEPT\n"); } else { printf("%s\n", SDLNet_GetError()); SDLNet_TCP_Close(server_sock); break; } while(1){ SDL_Delay(100); } SDLNet_TCP_Close(accept_sock); SDLNet_TCP_Close(server_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
SDLNet_TCP_Accept()が受け入れソケット作成処理です。引数にはサーバーソケットを指定します。
実行すると、エラーになって終了します。
これはクライアント側から接続がないために受け入れソケットが作成できないためです。
そこでクライアント側から接続されるまで待つようにします。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define SERVER_HOST 5555 int main(int argc, char* argv[]){ IPaddress server_ip; TCPsocket accept_sock; TCPsocket server_sock; char data[1024]; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); 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){ accept_sock = SDLNet_TCP_Accept(server_sock); if(accept_sock != NULL){ printf("ACCEPT\n"); while(1){ SDL_Delay(100); } } SDL_Delay(100); } SDLNet_TCP_Close(accept_sock); SDLNet_TCP_Close(server_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
これを実行すると、
待ち状態になりますので、クライアントプログラムを使って接続します。接続すると、サーバー側にACCEPTと表示されます。
SDLNet_TCP_Recv()を追加し、受信側プログラムを完成させます。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define SERVER_HOST 5555 int main(int argc, char* argv[]){ IPaddress server_ip; TCPsocket accept_sock; TCPsocket server_sock; char data[1024]; int result; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); 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){ accept_sock = SDLNet_TCP_Accept(server_sock); if(accept_sock != NULL){ printf("ACCEPT\n"); while(1){ result = SDLNet_TCP_Recv(accept_sock, data, sizeof(data) - 1); if(result <= 0){ printf("DISCONNECT\n"); break; } data[result] = 0x00; printf("%s\n", data); } } SDL_Delay(100); } SDLNet_TCP_Close(accept_sock); SDLNet_TCP_Close(server_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
SDLNet_TCP_Recv()は第1引数のソケットから、第3引数サイズを最大サイズとしたデータを、第2引数にセットする処理です。受信するか、接続が切れる、エラーが発生するまで処理は戻りません。
返り値は受信サイズで、0以下の場合はエラーまたは接続切断を意味します。
これで受信できるようになりました。受信データはprintf()で画面に表示されます。
次にクライアント側です。クライアント側は簡単で、先ほどのプログラムに、SDLNet_Send()を追加すれば、送信プログラムになります。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define REMOTE_HOST_IP "192.168.0.3" #define REMOTE_HOST_PORT 5555 int main(int argc, char* argv[]){ IPaddress client_ip; TCPsocket client_sock; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); SDLNet_ResolveHost(&client_ip, REMOTE_HOST_IP, REMOTE_HOST_PORT); client_sock = SDLNet_TCP_Open(&client_ip); do { if(client_sock != NULL){ printf("Open\n"); } else { printf("%s\n", SDLNet_GetError()); break; } SDLNet_TCP_Send(client_sock, "AAA", 3); SDLNet_TCP_Close(client_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
SDLNet_TCP_Send()は第1引数のソケットに第2引数のデータを第3引数のサイズ送信する、という関数です。返り値として、送信できたサイズが返ります。指定したサイズ以下が返ってきたら、何かエラーがあったと考えられます。
このプログラムは送信したらすぐに終了するので、受信側では、受信後、すぐに切断を検知するはずです。
受信プログラムは切断後、再び接続待ち処理に入るので、もう一度送信プログラムを動かせば、送信内容を表示します。
送受信処理
クライアントはSDLNet_Open()で接続したソケットにSDLNet_TCP_Send()で送信できました。サーバー側はSDLNet_TCP_Accept()で受け入れたソケットにSDLNet_TCP_Send()で送信できます。
クライアント側プログラム
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define REMOTE_HOST_IP "192.168.0.3" #define REMOTE_HOST_PORT 5555 int main(int argc, char* argv[]){ IPaddress client_ip; TCPsocket client_sock; char data[1024]; int result; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); SDLNet_ResolveHost(&client_ip, REMOTE_HOST_IP, REMOTE_HOST_PORT); client_sock = SDLNet_TCP_Open(&client_ip); do { if(client_sock != NULL){ printf("Open\n"); } else { printf("%s\n", SDLNet_GetError()); break; } while(1){ result = SDLNet_TCP_Recv(client_sock, data, sizeof(data) - 1); if(result <= 0){ break; } data[result] = 0x00; printf("%s\n",data); } SDLNet_TCP_Close(client_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
サーバー側プログラム
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define SERVER_HOST 5555 int main(int argc, char* argv[]){ IPaddress server_ip; TCPsocket accept_sock; TCPsocket server_sock; char data[1024]; int result; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); 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){ accept_sock = SDLNet_TCP_Accept(server_sock); if(accept_sock != NULL){ printf("ACCEPT\n"); SDLNet_TCP_Send(accept_sock, "bbb", 3); while(1){ result = SDLNet_TCP_Recv(accept_sock, data, sizeof(data) - 1); if(result <= 0){ printf("DISCONNECT\n"); break; } data[result] = 0x00; printf("%s\n", data); } } SDL_Delay(100); } SDLNet_TCP_Close(accept_sock); SDLNet_TCP_Close(server_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
このプログラムではサーバー側が送信、クライアント側が受信、と、処理が逆になっています。
サーバプログラムから先に起動し、サーバーはクライアントの接続を待ちます。クライアントが接続したら、サーバーはクライアントに送信します。
さて、送信と受信を両方やりたいと思います。受信待ち状態になった場合、相手からの送信を受けないとずっと待ちつづけるため、ちょっと工夫が必要です。
以下に一例を示します。
クライアント側プログラム。安全性は保証しませんので、使用する/参考にする場合は注意してください。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define REMOTE_HOST_IP "192.168.0.3" #define REMOTE_HOST_PORT 5555 int thread_func(void* pointer){ char data[1024]; int result; TCPsocket client_sock = (TCPsocket)pointer; while(1){ fgets(data, sizeof(data), stdin); data[strlen(data) -1] = 0x00; result = SDLNet_TCP_Send(client_sock, data, strlen(data)); if(result < strlen(data)){ break; } } return 0; } int main(int argc, char* argv[]){ IPaddress client_ip; TCPsocket client_sock; SDL_Thread *thread; char data[1024]; int result; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); SDLNet_ResolveHost(&client_ip, REMOTE_HOST_IP, REMOTE_HOST_PORT); client_sock = SDLNet_TCP_Open(&client_ip); do { if(client_sock != NULL){ printf("Open\n"); } else { printf("%s\n", SDLNet_GetError()); break; } thread = SDL_CreateThread(thread_func, client_sock); while(1){ result = SDLNet_TCP_Recv(client_sock, data, sizeof(data) - 1); if(result <= 0){ break; } data[result] = 0x00; printf("RECV: %s\n", data); } SDL_KillThread(thread); SDLNet_TCP_Close(client_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
スレッドを作って送信側と受信側を独立させています。これで入力待ちのときにデータを受信しても対応できますし、受信待ちの時もキーボード入力が可能となります。
次にサーバー側のプログラムです。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define SERVER_HOST 5555 int main(int argc, char* argv[]){ IPaddress server_ip; TCPsocket accept_sock; TCPsocket server_sock; char data[1024]; int result; int value; SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); 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){ accept_sock = SDLNet_TCP_Accept(server_sock); if(accept_sock != NULL){ printf("ACCEPT\n"); while(1){ result = SDLNet_TCP_Recv(accept_sock, data, sizeof(data) - 1); if(result <= 0){ printf("DISCONNECT\n"); break; } data[result] = 0x00; value = atoi(data); printf("RECV: %s -> %d -> %d\n", data, value, value+1); value++; sprintf(data, "%d", value); SDLNet_TCP_Send(accept_sock, data, strlen(data)); printf("SEND: %s\n", data); } } SDL_Delay(100); } SDLNet_TCP_Close(accept_sock); SDLNet_TCP_Close(server_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
受信したデータを数値変換し、1加えて返信するプログラムです。これで、クライアント側には送信した数字+1が返ってきて、それが画面に表示されます。
複数のホストと接続する
上のサーバープログラムを1つ起動し、クライアントプログラムを2つ起動してクライアントプログラムから数字を入力してみてください。
(クライアントプログラム1) $ ./a.out Open 10 RECV: 11
(クライアントプログラム2) $ ./a.out Open 20
片方のプログラム(クライアントプログラム1)からきたデータしかサーバープログラムは受信できていないことがわかります。
クライアントプログラムが複数のホストにつなぎにいくには、SDLNet_TCP_Open()を複数のホストに対して行えばよいことは簡単に想像できます。
では、サーバープログラムが複数の接続を受け入れるにはどうすればいいでしょうか。
サーバーの場合はSDL_TCP_Accept()を複数回実行することにより、複数の接続を受け入れることが可能になります。ただし、接続はいつ行われるか分からない&受信待ちになると処理が戻らないため、マルチスレッドで並行処理を行います。以下に一例を示します。
#include <SDL/SDL.h> #include <SDL/SDL_net.h> #define SERVER_HOST 5555 #define MAX_CONNECTION 2 typedef struct { int connection_no; TCPsocket sock; } thread_arg; typedef struct { SDL_Thread *thread; thread_arg arg; } thread_set; int thread_func(void* pointer){ char data[1024]; int result; int value; thread_arg *arg; TCPsocket accept_sock; arg = (thread_arg*)pointer; accept_sock = arg->sock; while(1){ result = SDLNet_TCP_Recv(accept_sock, data, sizeof(data) - 1); if(result <= 0){ printf("Host%d: DISCONNECT\n",arg->connection_no); break; } data[result] = 0x00; value = atoi(data); printf("Host %d: RECV: %s -> %d -> %d\n", arg->connection_no, data, value, value + 1); value ++; sprintf(data, "%d", value); SDLNet_TCP_Send(accept_sock, data, strlen(data)); printf("Host %d: SEND: %s\n", arg->connection_no, data); } arg->connection_no = -1; SDLNet_TCP_Close(accept_sock); return 0; } int main(int argc, char* argv[]){ IPaddress server_ip; TCPsocket server_sock; int i; thread_set t_set[MAX_CONNECTION]; for(i = 0; i < MAX_CONNECTION; i++){ t_set[i].arg.connection_no = -1; } SDL_Init(SDL_INIT_EVERYTHING); SDLNet_Init(); 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(t_set[i].arg.connection_no == -1){ t_set[i].arg.sock = SDLNet_TCP_Accept(server_sock); if(t_set[i].arg.sock != NULL){ printf("ACCEPT Host %d\n", i); t_set[i].arg.connection_no = i; t_set[i].thread = SDL_CreateThread(thread_func, &t_set[i].arg); } else { SDL_Delay(100); i--; } } } SDL_Delay(100); } for(i = 0; i < MAX_CONNECTION; i++){ if(t_set[i].arg.connection_no != -1){ SDL_KillThread(t_set[i].thread); SDLNet_Close(t_set[i].arg.sock); t_set[i].arg.connection_no = -1; } } SDLNet_TCP_Close(server_sock); } while(0); SDLNet_Quit(); SDL_Quit(); return 0; }
実行例は次のとおり
(クライアントプログラム1) $ ./a.out Open 10 RECV: 11 30 RECV: 31 40 RECV: 41
(クライアントプログラム2) $ ./a.out Open 20 RECV: 21 50 RECV: 51
今度はちゃんと受信/送信できています。
MAX_CONNECTIONの数だけ応答することができます。なお、クライアント側から接続することだけはできるのでその点は注意が必要です。
おまけ
今回のプログラムは特に終了処理を入れていません。終了しなくなったプログラムは「kill -9」で終了させましょう。
その他
- なぜすべての実行例の画像がないのか?
- スクリーンショット撮り忘れたから
- なぜクライアント側プログラムは画像ではないのか?
- 個人情報保護法的にヤバイものがうつっていたから
- ゲーム作りの方が停滞しているけど?
- いい加減ファイル分割しようと思って分割したら19ファイルになってしまった(これ書いている現在)。ドバッと並べるわけにもいかないのでどうしようか考えている最中。忘れたころに再開していると思いますので、たまに覗いてみてください。
- 今回の更新長くない?
- 私もそう思います。
以上、すべて独り言でした。
※メール、リンクは適当に。
Copyright (c) 2009 greencap