Androidのソケット通信をCで書いてみた
Luaridaでサポートしているtcp通信のコマンド(sock.sendやsock.connectOpen)は、LuaVMからtcpポートを介してDalvikに渡って、そこからさらにtcpソケットを送信しています。
これでは二度手間なので、LuaVMから直接ソケット通信するようにプログラムを書き直してみた。それらのプログラムを防備録として、ここに書いておきます。
LuaのOpenLibに追加する
従来Java版Socket通信用に作成したLuaのOpenLibに追加します。nからはじまるコマンドが今回追加するコマンドです。
static const luaL_Reg SockTbl[] = { {"nsend", socknSend }, {"nrecv", socknRecv }, {"send", sockSend }, {"recv", sockRecv }, {"getAddress", sockGetAddress }, {"connectOpen", sockConnectOpen }, {"listenOpen", sockListenOpen }, {"close", sockClose }, {"ngetAddress", socknGetAddress }, {"nconnectOpen", socknConnectOpen }, {"nlistenOpen", socknListenOpen }, {"nclose", socknClose }, {NULL,NULL} };
ヘッダとグローバル変数
最初にSocketプログラムのビルドに使用したヘッダとグローバルな変数を書いておきます。 SockNum[2]と2つの配列となっているのは、Luaridaはソケットを2つしかオープンできない仕様としたからです。
ThOpLs構造体はpThread用の構造体です。
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/endian.h> #include <netdb.h> #include <linux/in.h> #include <sys/select.h> #include <net/if.h> #include <semaphore.h> #include <pthread.h> #include "luavm.h" #include "luavmExec.h" #include "luavmClient.h" #include "AridaSystem.h" int SockNum[2] = { -1, -1 }; //通信ソケット番号 struct sockaddr_in Saddr[2]; //ソケットデータ #define RECVBUF 1024 char Srecv[RECVBUF]; //受信バッファ //POSIX Threadに使う構造体です struct ThOpLs { volatile int status; //状態フラグ用変数 int num; //開くLuaソケット通信番号(0or1) unsigned long port; //ポート番号 char* hostname; //ホスト名orIPアドレス pthread_t th; sem_t sync; //セマフォ用変数(シンクロ用) };
sock.nconnectOpenコマンド
nconnectOpenコマンドはIPアドレスを指定してポートに接続するコマンドです。ソースは下記です。LuaコマンドのTimeOut引数は省略されたときに5secになります。通信の番号は1か2としているので、第一引数に応じてSockNum配列の要素をセットします。それが既にコネクトしているソケットであれば閉じます。
そして、コネクトしてソケットをオープンするスレッドを起動します。このスレッドが起動したことを確認した後、指定の秒数待ちます。その後、コネクトスレッドのstatus変数を確認して、コネクトに成功したか失敗したかを判断します。
socknConnectOpen()とthread_SockOpen()がバグっていたので、こちらの方で書き直しています。
//************************************************** // sock.nconnectOpen( Number, Address, Port [,TimeOut] ) // 戻り値 // 0:接続できませんでした // 1:接続できました //************************************************** int socknConnectOpen( lua_State *LuaLinkP ) { struct ThOpLs ol[1]; int rtn; unsigned long tim = 5; //デフォルトの待ち時間 5s struct timespec treq, trem; long i; lua_Number lnum; int n = lua_gettop( LuaLinkP ); if( n<3 ){ LuaErrorMes( LuaLinkP, (char*)"sock.nconnectOpen" ); return( 1 ); //戻り値は1つですよ。 } //Luaのソケット番号セット if( lua_tonumber( LuaLinkP, 1 )<=1 ){ ol[0].num = 0; } else{ ol[0].num = 1; } if( SockNum[ol[0].num]!=-1 ){ //ソケットcloseします LuaSocketClose(SockNum[ol[0].num]); SockNum[ol[0].num] = -1; } //ポート番号セット ol[0].port = (unsigned long)lua_tonumber( LuaLinkP, 3 ); //ホスト名orIPアドレスセット ol[0].hostname = (char*)luaL_checklstring( LuaLinkP, 2, NULL ); //タイムアウト秒数のセット if( n>3 ){ tim = (unsigned long)lua_tonumber( LuaLinkP, 4 ); } sem_init(&ol[0].sync, 0, 0); //syncセマフォの初期化 //pThreadを生成する rtn = pthread_create(&ol[0].th, NULL, thread_SockOpen, (void *) (&ol[0])); if (rtn != 0) { lua_settop(LuaLinkP, 0); //スタックのクリア lnum = 0; lua_pushnumber( LuaLinkP, lnum ); //1番目の戻り値 return( 1 ); //戻り値は1つですよ } //ソケットオープンスレッドが起動するまで待つ sem_wait(&ol[0].sync); //待ち時間の設定 treq.tv_sec = (time_t)0; treq.tv_nsec = 25000000; //25ms long li=tim*40; //最大指定の秒数待つ for(i=0; i<li; i++){ nanosleep(&treq, &trem); if( ol[0].status!=1 ){ break; } } //1なら終了していない lua_settop(LuaLinkP, 0); //スタックのクリア if( ol[0].status==1 ){ //デタッチしてスレッドを切れるようにする pthread_detach(ol[0].th); treq.tv_sec = (time_t)0; treq.tv_nsec = 20000000; //20ms nanosleep(&treq, &trem); //20ms待つ //SockNumの初期化 SockNum[ol[0].num] = -1; lnum = 0; } else if( ol[0].status==2 ){ //失敗で戻ってきている lnum = 0; } else{ lnum = 1; } lua_pushnumber( LuaLinkP, lnum ); //1番目の戻り値 return( 1 ); //戻り値は1つですよ }
thread_SockOpen(void *thr)
ソケットをコネクトオープンするスレッドは下記のような感じです。statusを1にセットした後、sem_postを使って親プロセスに起動したことを知らせた後、コネクトしにいきます。
//************************************************************************* // ソケットポートのコネクトオープンスレッド // priv->status = 1:コネクト中, 2:失敗, 0:成功 //************************************************************************* void *thread_SockOpen(void *thr) { struct ThOpLs *priv = (struct ThOpLs *)thr; struct hostent *accessIP; //接続先IP int ret; priv->status = 1; //コネクト中フラグを立てておく sem_post(&priv->sync); //親プロセスに起動したことを知らせる。 SockNum[priv->num] = socket( AF_INET, SOCK_STREAM, 0 ); //接続用ソケットの生成 if( SockNum[priv->num]&0x80000000 ){ SockNum[priv->num] = -1; priv->status = 2; return (void *) NULL; } // サーバのIPアドレスを検索します accessIP = gethostbyname( priv->hostname ); Saddr[priv->num].sin_family = AF_INET; Saddr[priv->num].sin_addr.s_addr = *( (unsigned long *)(accessIP->h_addr_list[0]) ); //IP設定 Saddr[priv->num].sin_port = htons( priv->port ); //ポート設定 //接続する ret = connect( SockNum[priv->num], (struct sockaddr *)&Saddr[priv->num], sizeof(Saddr[priv->num]) ); if( ret!=0 ){ close( SockNum[priv->num] ); SockNum[priv->num] = -1; priv->status = 2; return (void *) NULL; } //成功 priv->status = 0; return (void *) NULL; }
LuaSocketClose
socknConnectOpen関数にソケットを閉じる関数が有りました。このソースを下記に書きます。shutdownした後しばらく待っています。昔NDSのWifiプログラムを作ったときに、Wifiの場合しばらく待たないとうまくcloseできなかった経験があるので、待ちを入れています。
//************************************************** // ソケットをクローズします //************************************************** void LuaSocketClose( int socknum ) { struct timespec treq, trem; //待ち時間の設定 treq.tv_sec = (time_t)0; treq.tv_nsec = 300000000; //300ms shutdown( socknum, 0 ); //ソケットをシャットダウンします //300ms待ちます nanosleep(&treq, &trem); close( socknum ); //ソケットを閉じます }
sock.nlistenOpenコマンド
次に、接続を受付けるコマンドのソースを下記に書きます。今、気づきました!このプログラムって、socknConnectOpen()と全く同じだ・・。違いは、Listenスレッドを起動しているところ。
//************************************************** // sock.nlistenOpen( Number, Port [,TimeOut] ) // 戻り値 // 0:接続待ちがタイムアウトしました // 1:接続できました //************************************************** int socknListenOpen( lua_State *LuaLinkP ) { struct ThOpLs ol[1]; int rtn; unsigned long tim = 5; //デフォルトの待ち時間 5s struct timespec treq, trem; long i; lua_Number lnum; int n = lua_gettop( LuaLinkP ); if( n<2 ){ LuaErrorMes( LuaLinkP, (char*)"sock.nlistenOpen" ); return( 1 ); //戻り値は1つですよ。 } //Luaのソケット番号セット if( lua_tonumber( LuaLinkP, 1 )<=1 ){ ol[0].num = 0; } else{ ol[0].num = 1; } if( SockNum[ol[0].num]!=-1 ){ //ソケットcloseします LuaSocketClose(SockNum[ol[0].num]); SockNum[ol[0].num] = -1; } //ポート番号セット ol[0].port = (unsigned long)lua_tonumber( LuaLinkP, 2 ); //ホスト名orIPアドレスセット ol[0].hostname = 0; //タイムアウト秒数のセット if( n>2 ){ tim = (unsigned long)lua_tonumber( LuaLinkP, 3 ); } sem_init(&ol[0].sync, 0, 0); //syncセマフォの初期化 //pThreadを生成する rtn = pthread_create(&ol[0].th, NULL, thread_SockListen, (void *) (&ol[0])); if (rtn != 0) { lua_settop(LuaLinkP, 0); //スタックのクリア lnum = 0; lua_pushnumber( LuaLinkP, lnum ); //1番目の戻り値 return( 1 ); //戻り値は1つですよ } //pThreadが起動するまで待つ sem_wait(&ol[0].sync); //待ち時間の設定 treq.tv_sec = (time_t)0; treq.tv_nsec = 25000000; //25ms //最大指定の秒数待つ long il = tim*40; for(i=0; i<il; i++){ nanosleep(&treq, &trem); if( ol[0].status!=1 ){ break; } } //1なら終了していない lua_settop(LuaLinkP, 0); //スタックのクリア if( ol[0].status==1 ){ //デタッチしてスレッドを切れるようにする pthread_detach(ol[0].th); treq.tv_sec = (time_t)0; treq.tv_nsec = 20000000; //20ms nanosleep(&treq, &trem); //20ms待つ //SockNumの初期化 SockNum[ol[0].num] = -1; lnum = 0; } else if( ol[0].status==2 ){ //失敗で戻ってきている lnum = 0; } else{ lnum = 1; } lua_pushnumber( LuaLinkP, lnum ); //1番目の戻り値 return( 1 ); }
thread_SockListen(void *thr)
外部からのポート接続を待つスレッドです。ソースは下記のような感じです。コネクトスレッドと同様、statusを1にセットした後、sem_postを使って親プロセスに起動したことを知らせた後、コネクト待ちに入ります。
実際には、Listen用のソケットを生成してバインドします。そして、接続をListenで待ちます。接続があったらAcceptしてソケット番号と接続アドレスを取得し、Listen用のソケットをクローズして終了します。
//************************************************************************* // ソケットポートのオープンスレッド // priv->status = 1:コネクト中, 2:失敗, 0:成功 //************************************************************************* void *thread_SockListen(void *thr) { struct ThOpLs *priv = (struct ThOpLs *)thr; int ret; int sockListen; // 待受用ソケット struct sockaddr_in addrListen; priv->status = 1; //コネクト中フラグを立てておく sem_post(&priv->sync); //親プロセスに起動したことを知らせる。 sockListen = socket( AF_INET, SOCK_STREAM, 0 ); //接続用ソケットの生成 if( sockListen&0x80000000 ){ SockNum[priv->num] = -1; priv->status = 2; return (void *) NULL; } //アドレスをバインドする前に、SO_REUSEADDRオプションを指定して、on=1に設定し、 //アドレスの再利用を即有効にします。(アドレスの再利用を禁止するには、0を設定します) int on = 1; ret = setsockopt( sockListen, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ); // サーバのIPアドレスを検索します addrListen.sin_family = AF_INET; addrListen.sin_addr.s_addr = INADDR_ANY; //IP無視設定 addrListen.sin_port = htons( priv->port ); //ポート設定 //ソケットをバインドする ret = bind( sockListen, (struct sockaddr *)&addrListen, sizeof(addrListen) ); if( ret!=0 ){ close( sockListen ); SockNum[priv->num] = -1; priv->status = 2; return (void *) NULL; } //ポートを見に行きます ret = listen( sockListen, 5 ); if( ret!=0 ){ close( sockListen ); SockNum[priv->num] = -1; priv->status = 2; return (void *) NULL; } int cbAddrAccept = sizeof( Saddr[priv->num] ); //接続ソケットに対するソケット生成 SockNum[priv->num] = accept( sockListen, (struct sockaddr *)&Saddr[priv->num], &cbAddrAccept); if( SockNum[priv->num]&0x80000000 ) { close( sockListen ); SockNum[priv->num] = -1; priv->status = 2; return (void *) NULL; } close( sockListen ); //listenソケットのクローズ //成功 priv->status = 0; return (void *) NULL; }
sock.ncloseコマンド
接続を閉じるコマンドのソースを下記に書きます。Luaridaで指定したソケットの番号を取得して、LuaSocketClose()関数を読んでいるだけです。
//************************************************** // sock.nclose( Number ) // 戻り値 // 0: closeできませんでした // 1: closeできました //************************************************** int socknClose( lua_State *LuaLinkP ) { int t; int n = lua_gettop( LuaLinkP ); if( n<1 ){ LuaErrorMes( LuaLinkP, (char*)"sock.nclose" ); return( 1 ); //戻り値は1つですよ。 } //Luaのソケット番号セット if( lua_tonumber( LuaLinkP, 1 )<=1 ){ t = 0; } else{ t = 1; } if( SockNum[t]==-1 ){ lua_settop(LuaLinkP, 0); //スタックのクリア lua_pushnumber( LuaLinkP, 0 ); //0をセット return( 1 ); //戻り値は1つですよ } //ソケットcloseします LuaSocketClose( SockNum[t] ); SockNum[t] = -1; Saddr[t].sin_addr.s_addr = 0; //アドレスの初期化 lua_settop(LuaLinkP, 0); //スタックのクリア lua_pushnumber( LuaLinkP, 1 ); //1をセット return( 1 ); //戻り値は1つですよ }
sock.nsendコマンド
sock.nsendはデータを送信するコマンドです。ソースを下記に書きます。send関数でLuaridaの引数データを送信しています。
//************************************************** // sock.nsend( Number, Data, Length ) // 戻り値 // 送信成功: 送信データ長 // 送信失敗: -1 //************************************************** int socknSend( lua_State *LuaLinkP ) { char *str; int tlen, t; lua_Number lnum; int n = lua_gettop( LuaLinkP ); if( n<3 ){ LuaErrorMes( LuaLinkP, (char*)"sock.nsend" ); return( 1 ); //戻り値は1つですよ。 } //Luaのソケット番号セット if( lua_tonumber( LuaLinkP, 1 )<=1 ){ t = 0; } else{ t = 1; } if( SockNum[t]!=-1 ){ str = (char*)luaL_checklstring( LuaLinkP, 2, NULL ); tlen = (int)lua_tonumber( LuaLinkP, 3 ); lnum = send( SockNum[t], str, tlen, 0 ); } else{ lnum = -1; } lua_settop(LuaLinkP, 0); lua_pushnumber( LuaLinkP, lnum ); //1番目の戻り値 return( 1 ); //戻り値は1つです }
sock.nrecvコマンド
sock.nrecvはデータを受信するコマンドです。ソースを下記に書きます。受信はファイルディスクリプタをセットして、selectでソケットにデータを受信するまで待ちます。selectに戻り値があるとrecvでデータを取得します。受信はselectを使っています。スレッドを使うよりも処理が速かったので、こうしました。
//************************************************** // sock.recv( Number [,TimeOut] ) // 戻り値 // 受信成功: 受信データ, 受信データ長 // 受信失敗: nil, -1 //************************************************** int socknRecv( lua_State *LuaLinkP ) { unsigned long tim = 5; //デフォルトの待ち時間 5s int t; ssize_t recvd_len = 0; int ret; fd_set fds; struct timeval tv; int n = lua_gettop( LuaLinkP ); if( n<1 ){ LuaErrorMes( LuaLinkP, (char*)"sock.nrecv" ); return( 1 ); //戻り値は1つですよ。 } //Luaのソケット番号セット if( lua_tonumber( LuaLinkP, 1 )<=1 ){ t = 0; } else{ t = 1; } if( SockNum[t]==-1 ){ lua_settop(LuaLinkP, 0); //スタックのクリア lua_pushlstring( LuaLinkP, Srecv, 0 ); //1番目の戻り値 lua_pushnumber( LuaLinkP, -1 ); //2番目の戻り値 return( 2 ); //戻り値は2つです } //タイムアウト秒の読み込み if( n>1 ){ tim = (unsigned long)lua_tonumber( LuaLinkP, 2 ); } FD_ZERO( &fds ); //ファイルディスクリプタの識別子初期化 FD_SET( SockNum[t], &fds ); //socketのファイルディスクリプタ識別子に1をセット tv.tv_sec = tim; tv.tv_usec = 0; //ポートの監視、(受信のみ監視、他はNULL) ret = select( (int)SockNum[t]+1, &fds, NULL, NULL, &tv ); if( ret!=0 ){ recvd_len = recv( SockNum[t], Srecv, RECVBUF, 0 ); } else{ lua_settop(LuaLinkP, 0); //スタックのクリア lua_pushlstring( LuaLinkP, Srecv, 0 ); //1番目の戻り値 lua_pushnumber( LuaLinkP, -1 ); //2番目の戻り値 return( 2 ); //戻り値は2つです } lua_settop(LuaLinkP, 0); //スタックのクリア lua_pushlstring(LuaLinkP, Srecv, recvd_len); //1番目の戻り値 lua_pushnumber( LuaLinkP, recvd_len ); //2番目の戻り値 return( 2 ); //戻り値は2つですよ。 }
sock.ngetAddressコマンド
sock.ngetAddressはIPアドレスを取得するコマンドです。ただし、ipv4しか対応していません。
Luaridaで指定したソケットの番号に保持しているIPアドレスを返すだけです。ただし、引数Loaclが1のときは、自身のIPアドレスを返します。自身のIPといっても、ソースを見て分かるように、"wlan0"に割りついているIPを返しているだけです。wifi以外を使っている場合は"eth0"など指定する必要があると思います。BlueToothの場合はどうなるのかな?
後、manufestにandroid.permission.ACCESS_WIFI_STATEを書かなくても、Nativeで書けば、WifiのIPアドレスは取得できてしまうようです。いいのかな?
//************************************************** // sock.ngetAddress( Number [,Local] ) // 戻り値 // 接続先のIPアドレスを返します。 // Localが1の時には自身のWifiのIPアドレスを返します。 //************************************************** int socknGetAddress( lua_State *LuaLinkP ) { char empty[1] = {0}; char *str; int t; struct ifreq ifr; int dummySock; int p = 0; int n = lua_gettop( LuaLinkP ); if( n<1 ){ LuaErrorMes( LuaLinkP, (char*)"sock.ngetAddress" ); return( 1 ); //戻り値は1つですよ。 } t = (int)lua_tonumber( LuaLinkP, 1 ); //Luaのソケット番号セット if( lua_tonumber( LuaLinkP, 1 )<=1 ){ t = 0; } else{ t = 1; } if( n>1 ){ p = (int)lua_tonumber( LuaLinkP, 2 ); } if( SockNum[t]==-1 ){ if( p==1 ){ dummySock = socket( AF_INET, SOCK_STREAM, 0 ); //ダミーソケットの生成 if( dummySock&0x80000000 ){ str = empty; } else{ //IPv4のIPアドレスを取得したい ifr.ifr_addr.sa_family = AF_INET; //eth0のIPアドレスを取得したい strncpy(ifr.ifr_name, "wlan0", IFNAMSIZ-1); ioctl(dummySock, SIOCGIFADDR, &ifr); str =(char*)inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); close( dummySock ); //ダミーソケットのクローズ } } else{ str = empty; } } else{ if( p==1 ){ //IPv4のIPアドレスを取得したい ifr.ifr_addr.sa_family = AF_INET; //eth0のIPアドレスを取得したい strncpy(ifr.ifr_name, "wlan0", IFNAMSIZ-1); ioctl(SockNum[t], SIOCGIFADDR, &ifr); str =(char*)inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); } else{ str = (char*)inet_ntoa(Saddr[t].sin_addr); } } lua_settop(LuaLinkP, 0); //スタックのクリア lua_pushstring( LuaLinkP, str ); return( 1 ); //戻り値は1つですよ。 }