Unixドメインソケットを利用してプロセス間通信を行う


Androidのアプリケーションは、それぞれが別プロセスで起動しているため、
他アプリケーションとの通信を行う際には、IntentやServiceなどを利用したデータの受け渡しを行う事が多いでしょう。
本エントリでは、Unixドメインのソケット通信を利用して他アプリケーションと通信する方法を紹介したいと思います。
Unixドメインのソケット通信とは、通常インターネットで利用しているソケット通信(INETドメインソケット通信)とは異なり、ネットワークを介さず、端末内の別プロセスとソケット通信を行います。
INETドメインのソケット通信の記事はこちらになります。
Socketクラスを用いてソケット通信をする
ServerSocketを使用してクライアントからデータを受信をする

本エントリでは、Server側、Client側共にJavaで作成していますが、もちろんNDKを利用したC/C++ライブラリとの通信も可能です。
Unixドメインソケットを利用する為に利用するクラス一覧は以下の表の通りです。

■表1 利用するクラス
[table “236” not found /]

本エントリで作成するもの

本エントリでは2アプリケーション間のプロセス間通信を行う為、
Server/Clientの二つのアプリケーションを例に挙げ解説しています。
2アプリケーションの構成は以下図の通りになります。

また、2つのアプリケーションは、以下のような起動順で立ち上げられ
通信を確立しています。

以降、Server側、Client側の順に取り扱っていきます。

それでは続きをどうぞ。

Serverを作成する

まず、Server側のソケットを作成します。
Server側の待ち受けソケットを開く為には、LocalServerSocketクラスacceptメソッドを利用します。
また、Unixドメインソケットを利用する場合も、UIスレッド上で通信を行わないほうがいいでしょう。

サンプルでは、Threadを起動し、Thread中でLocalServerSocketクラスのacceptメソッドを呼び出し、
Clientからのconnect要求を待ち受けています。
受信処理自体は簡略化していますが、通常のソケット通信のようにRead/WriteのStreamを取得し
様々なデータの送受信を行う事ができます。

■ src/UnixDomainSocketServerSampleActivity.java

public class UnixDomainSocketServerSampleActivity extends Activity implements Runnable{
	private Thread mLooper = null;

	LocalServerSocket mLocalServerSocket = null;
	LocalSocket mLocalSocket = null;
	private final String SOCKET_NAME = "org.techbooster.server";

	// ...省略

	@Override
	protected void onResume() {
		super.onResume();

		// ServerSocketの作成
		try {
			mLocalServerSocket = new LocalServerSocket(SOCKET_NAME);
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Threadの起動
		mLooper = new Thread(this);
		mLooper.start();
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		mLooper = null;
	}

	@Override
	public void run() {
		// Serverの起動
		try {
			// 待ち受け
			mLocalSocket = mLocalServerSocket.accept();

			// 受信
			int ret = 0;
			while((ret = mLocalSocket.getInputStream().read()) != -1){
				Log.v("Techbooster.LocalServerSocketSample","ret =" + ret);
			}

			Log.v("Techbooster.LocalServerSocketSample","ret =" + ret);

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// 切断
		try {
			mLocalSocket.close();
			mLocalServerSocket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

1行目の通り、Runnableインターフェースをimplementsし、runメソッドにソケット通信関連の処理を記述しています。
11行目からのonResumeメソッド内部でLocalServerSocketクラスのインスタンスを作成し、Threadの起動を行っています。
16行目で、LocalServerSocketを作成しています。LocalServerSocketの作成時には、ソケット名を指定する必要があります。

37行目のacceptメソッドの実行で、LocalServerSocketを開き、Clientからの要求を待ち受けています。
acceptメソッドを実行すると、Unixドメインソケットが開かれます。
開かれているUnixドメインソケットを確認するには、ADB接続下で下記コマンドを利用します。

$ cat /proc/net/unix                                 

上記コマンドの実行結果は以下図の通りです。
6行目で指定しているSOCKET_NAMEのソケットが開かれていることが確認できます。

37行目以降で、ソケット接続後の受信処理とソケットのクローズ処理を行っています。
これらの処理は、INETドメインソケットを利用した場合と変わらないため割愛します。

Clientを作成する

作成したServerに接続(Connect)を行い、ソケットへ書き込みを行うClientを作成します。
Client側のConnectメソッドには、LocalSocketクラスに対しServer側で指定したソケット名を、LocalSocketAddressクラス経由で引き渡します。

サンプルでは、接続の契機をボタン押下時として、onClickメソッド呼び出し時にThreadを起動し、
Thread内でLocalSocketクラスのconnectメソッドを呼び出しています。
接続後には、適当なint型の数値3つを順に書き込みます。

■ src/UnixDomainSocketSampleActivity.java

public class UnixDomainSocketSampleActivity extends Activity implements Runnable,OnClickListener {
	private Thread mLooper = null;
	private final String SOCKET_NAME = "org.techbooster.server";
	LocalSocket mLocalSocket = null;

	// ...省略

	@Override
	public void run() {
		try {
			// ソケットの作成
			mLocalSocket = new LocalSocket();
			LocalSocketAddress addr = new LocalSocketAddress(SOCKET_NAME);

			// 接続
			mLocalSocket.connect(addr);

			// write
			int data1 = 0xff;
			int data2 = 0xaa;
			int data3 = 0x88;
			mLocalSocket.getOutputStream().write(data1);
			mLocalSocket.getOutputStream().write(data2);
			mLocalSocket.getOutputStream().write(data3);
		} catch (IOException e) {
			e.printStackTrace();
		}

		// 切断
		try {
			mLocalSocket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void onClick(View v) {
		switch(v.getId()){
		case R.id.button1:
			mLooper.start();
			Toast.makeText(this, "Connect Server!", Toast.LENGTH_LONG);
			break;
		default:
			break;
		}
	}
}

13行目で、接続先のソケットを指定しています。
接続先ソケットの指定には、LocalSocketAddressクラスを利用し、コンストラクタにServer側で指定したソケット名を引き渡します。
15行目のconnectメソッドを呼び出した後、ソケット通信が確立します。
通常のINETドメインソケット通信と同様に、LocalSocketクラスのgetOutputStreamメソッドからストリームを取得し、書き込みを行っています。
30行目の通り、通信処理の終了時には必ずソケットを閉じるようにしましょう。

サンプルの実行結果

上記のServerとClientアプリを作成し、LocalSocketでの接続/通信を行った結果は以下の通りになります。
Server側のアプリケーションで、Client側で書き込んだ数字が読み取れていることが確認できています。