一覧
パイプでの接続を記述したシェルスクリプトを用意するのが簡単です.例えば,構文解析システム KNP を動作させるには,以下のようなシェルスクリプトを用意します.
#!/bin/bash
juman | knp
このスクリプトを run_knp.sh として保存した上で,以下のようにサービス定義XMLを用意します.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="target"
class="jp.go.nict.langrid.servicecontainer.handler.TargetServiceFactory">
<property name="service">
<bean class="jp.go.nict.rasc.wrapper.StandardInputService">
<property name="cmdLine" value="sh ___BASE_DIR___/run_knp.sh" />
<property name="delimiterIn" value="\n" />
<property name="delimiterOut" value="EOS\n" />
</bean>
</property>
</bean>
</beans>
MessagePack RPCでユーザプログラムを呼び出す の手順に従い,このサービス定義XMLを指定してRaSCサービスを起動すると,パイプで接続された複数のユーザプログラムをRaSC上で稼働させることができます.
マルチコアCPUを生かして並列実行する に従って,並列実行の設定をしたRaSCサービスを起動します.
以下のようなJavaプログラムでRaSCサービスを呼び出すことで,標準入力からの読み取りと,並列処理を行えます.
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.util.Arrays;
import org.apache.commons.lang.StringUtils;
import jp.go.nict.langrid.client.msgpackrpc.MsgPackClientFactory;
import jp.go.nict.wisdom.wrapper.api.TextAnalysisService;
public class RaSCClient {
public static void main(String[] args) throws Exception {
try(MsgPackClientFactory factory = new MsgPackClientFactory()){
TextAnalysisService client = factory.create(
TextAnalysisService.class,
new InetSocketAddress(args[0], Integer.parseInt(args[1])));
try(InputStreamReader isr = new InputStreamReader(System.in, "UTF-8");
BufferedReader br = new BufferedReader(isr)) {
String[] list = new String[1000];
int count = 0;
while((list[count] = br.readLine()) != null) {
count++;
if (count == list.length){
String[] ret = client.analyzeArray(list);
System.out.println(StringUtils.join(ret, "\n"));
count = 0;
}
}
if(count > 0){
String[] ret = client.analyzeArray(Arrays.copyOf(list, count));
System.out.println(StringUtils.join(ret, "\n"));
}
}
}
}
}
コンパイルと実行には,RaSCコアパッケージ を使うのが簡単です(手順は MessagePack RPCでユーザプログラムを呼び出す を参照してください).以下のコマンドでコンパイルできます.
javac -cp lib/*: RaSCClient.java
ホスト名とポートを指定し,以下のように利用できます.ここでは,ホストは localhost, ポートは 19999 でRaSCサービスが起動しているものとします.
cat INPUT_TXT | java -cp ./lib/*: RaSCClient localhost 19999
サービス定義XMLに指定するコマンドラインで,ディレクトリ名やファイル名に空白が含まれると,MessagePack RPCでユーザプログラムを呼び出す に示した記述では正しく動作しません.
プロパティ cmdLine の代わりに,cmdArray を使って指定してください.以下は,Windows環境におけるmecabのデフォルトパスを指定する例です.
<property name="cmdArray">
<list>
<value>C:\Program Files\Mecab\bin\mecab</value>
<value>-O</value>
<value>wakati</value>
</list>
</property>
cmdArrayプロパティとcmdLineプロパティが同時に設定されるとエラーになります.
サービス定義XML の includeDelim を true にしてください.
String getStatus() というメソッドを呼ぶと,設定されたコマンドラインと,プールされたプロセス数が返されます. 以下に呼出例を示します. SERVICE_HOST, SERVICE_PORT はそれぞれ,RaSCサービスが起動しているホストとポートであるとします.
呼出例: Java
TextAnalysisService client = factory.create(TextAnalysisService.class,
new InetSocketAddress(SERVICE_HOST, SERVICE_PORT));
String ret = client.getStatus();
呼出例: Perl
my $client = AnyEvent::MPRPC::Client->new(
host => "SERVICE_HOST", port => "SERVICE_PORT"
);
my $ret = $client->call('getStatus')->recv;
呼出例: Python
client = msgpackrpc.Client(msgpackrpc.Address("SERVICE_HOST", SERVICE_PORT))
ret = client.call('getStatus')
MessagePack RPCでユーザプログラムを呼び出す で起動したMeCabサービスの場合の,結果の例を示します.サービス定義XMLに設定されたコマンドラインと,実行中のプロセス数・上限のプロセス数が返されます.
Command line: /usr/local/bin/mecab
Pooled processes: 1 / 20
クライアントから呼び出しを行っても結果が得られず,サービス定義XMLに設定したタイムアウト時間が経過してからエラーが返ることがあります. ユーザプログラムが実際には短時間で終了しているとすると,以下のような問題が考えられます.
入力デリミタ,出力デリミタは, サービス定義XML に定義された,入力や出力の単位を認識するための区切りです.
例えば,ユーザプログラムが入力デリミタとして [END_OF_INPUT]\n (末尾に改行あり)という文字列を認識するとします. このとき,RaSCの設定で [END_OF_INPUT] (末尾に改行無し) を設定してしまうと,ユーザプログラムは改行コードを待ち続け,応答がなくなったように見えます.RaSCは設定した時間以上応答がない場合,プログラムを終了させ,再起動します(再起動が行われたことは,RaSCサービスのログに出力されます).
同様の問題は,出力についても発生します.例えば,ユーザプログラムが出力デリミタとして [END_OF_OUTPUT] (末尾に改行無し)という文字列を出力するとします. 一方,RaSCの設定で [END_OF_INPUT]\n (末尾に改行あり) を設定してしまうと,RaSCは改行コードを待ち続けます.
RaSCサービスからの応答がない場合には,入出力デリミタ(改行コードの有り無し,改行コードの種類),クライアントからの呼び出しにおける入力をチェックしてみてください.
RaSCサービス化可能なプログラム に説明があります.
ユーザプログラムをRaSCサービス上で稼働させるには,以下が必要です.
以下は,これを実現するためのCRF++の改修例です. CRF++には標準入力を使用するオプションがあるため,入出力のデリミタを決めます.ここでは,入力のデリミタに [END_OF_INPUT], 出力のデリミタに EOS を用いるとします. そのため,下記のソースコードのハイライトされた箇所を追加し,1行読み込んだ内容が [END_OF_INPUT] だった場合に EOS\n を標準出力に書き込む処理を加えます.
CRF++-0.58/tagger.cpp
bool TaggerImpl::read(std::istream *is) {
scoped_fixed_array<char, 8192> line;
clear();
for (;;) {
if (!is->getline(line.get(), line.size())) {
is->clear(std::ios::eofbit|std::ios::badbit);
return true;
}
if(std::strcmp(line.get(), "[END_OF_INPUT]") == 0){
std::cout << "EOS\n";
return true;
}
if (line[0] == '\0' || line[0] == ' ' || line[0] == '\t') {
break;
}
if (!add(line.get())) {
return false;
}
}
return true;
}
サイズの大きいデータを一度に処理しようとすると,バッファが溢れ,エラーになることがあります. サービス定義XML の設定で,バッファサイズを変更することができます.