コンピュータを楽しもう!!

今、自分が面白くていろいろやってみたことを書き綴りたいと思います。連絡先はtarosa.yでgmail.comです。

Androidのソケット通信をCで書いてみた

Luaridaでサポートしているtcp通信のコマンド(sock.sendsock.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した後しばらく待っています。昔NDSWifiプログラムを作ったときに、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で書けば、WifiIPアドレスは取得できてしまうようです。いいのかな?

//**************************************************
// 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つですよ。
}


以上、防備録として書きました。Luarida新版のリリースは来週中にしたいと思います。