2011年03月25日

実践SNMP+Java-非同期ポーリング

今日はSNMP4Jライブラリを利用した非同期ポーリング(SNMPリクエストの送受信)について紹介します。

SNMP4Jでは同期(Synchronous)又は非同期(Asynchronous)なSNMPリクエストの送信が可能です。

非同期でリクエストを送信する場合、Snmpクラスに用意されているsend(PDU pdu, Target target, java.lang.Object userHandle, ResponseListener listener) メソッドを利用します。

サンプル・コード


// SNMP通信パラメータを設定するクラス。
CommunityTarget comTgt = new CommunityTarget();
comTgt.setAddress(new UdpAddress("192.168.0.2/161")); // UDPアドレス(IPアドレス/SNMPポート)
comTgt.setCommunity(new OctetString("public")); // SNMPコミュニティ名
comTgt.setTimeout(1000); // タイムアウト時間(ミリ秒)
comTgt.setRetries(0); // リトライ数
comTgt.setVersion(SnmpConstants.version1); // SNMPバージョン

// SNMP操作の基本になるクラス。
Snmp snmp = null;
// DefaultTcpTransportMappingクラスもあります
DefaultUdpTransportMapping utm = null;
try {
        utm = new DefaultUdpTransportMapping();
        snmp = new Snmp(utm);
        snmp.listen(); // ディパッチャーの起動、ローカルUDPポートのオープン、待機
        // SNMP Get
        long start = System.currentTimeMillis();
        
        for (int i=0; i<10; i++) {
                // PDUを生成します。
                PDU pdu = new PDU();
                pdu.setType(PDU.GET);
                // PDUにバーバインドを格納します。
                OID oid = new OID(
                                ".1.3.6.1.2.1.1.3.0" // system.sysUpTime.0
                                );
                pdu.add(new VariableBinding(oid));
                // リスナー
                ResponseListener listener = new ResponseListener() {
                        public void onResponse(ResponseEvent event) {
                                // Always cancel async request when response has been received
                                // otherwise a memory leak is created! Not canceling a request
                                // immediately can be useful when sending a request to a broadcast address.
                                ((Snmp) event.getSource()).cancel(event.getRequest(), this);
                                PDU resPDU = event.getResponse();                                               
                                if (resPDU != null) {
                                        System.out.println("PDU -> " + resPDU);
                                } else {
                                        System.out.println("PDU is null.");
                                }
                        }
                };                              
                // userHandle - an user defined handle that is returned 
                // when the request is returned via the listener object.
                Object userHandle = null;
                snmp.get(pdu, comTgt, userHandle, listener);    
        }
        
        long end = System.currentTimeMillis();
        long diff = end - start;
        
        System.out.println("sending message was finished. " + diff + " millis.");
         
        try {
                wait();
        } catch (InterruptedException e) {
                e.printStackTrace();
        }
        // 全てのSNMPリクエストのハンドリング(受信/タイムアウト)が終了してから
        // snmp.close()する必要がある。
        // snmp.close();
} catch (IOException e) {
        e.printStackTrace();
}



解説


CommunityTargetインスタンスの生成・設定、SNMPインスタンスのサービス開始(snmp.listen();)までは過去に紹介したSNMP操作と変わりありません。
 
その後、forループ内で計10回、system.systeUpTime.0をSNMP GETし、これに要した時間を計測しています。
 
getメソッドに渡しているResponseListenerクラスはレスポンスを受信又はタイムアウトした際にSnmpインスタンスから呼び出されます(デザインパターンでいうオブザーバー・パターンですね)。

ちなみに、forループの中でPDUインスタンスを都度生成していますが、これはリクエスト毎にリクエストIDを自動生成しているからです。PDUインスタンスの生成を一度だけにしたい場合、2回目以降、PDU::setRequestID(Integer32 requestID) でリクエスト毎に一意なIDを設定します。

最後にこの方法ではリクエストの送信以外は非同期で処理が行われるので、forループを抜けた後はwait()しています。
 
これが無いとこのプログラムは送信しただけで終了してしまいます。

また、Snmpインスタンスのサービス(ローカル・ポートで待ち受けしているディスパッチャ)を終了するにはsnmp.close()が必要ですが、このメソッドは全てのレスポンスのハンドリングが終了してから呼び出す必要があります。

以上で解説は終了です。このプログラムに実際に存在しないSNMPエージェントのIPアドレスを指定して実行してみましょう。もし、リクエストの送信とレスポンスの受信が同期しているのであれば、タイムアウトが1000ミリ秒、リトライ数が0回ですから、1秒×10回すなわち10秒でforループが終了するはずですが、この例では数十ミリ秒で終了します。

sending message was finished. 11 millis.

PDU is null.

PDU is null.

PDU is null.

PDU is null.

PDU is null.

PDU is null.

PDU is null.

PDU is null.

PDU is null.

PDU is null.


タイムアウトを表すPDU is null.は約1秒後に一斉に表示されます。

最後にsnmp.getメソッドに渡しているuserHandle(Object)ですが、渡しておくとResponseListener::onResponseメソッド内でResponseEventオブジェクトから取り出して利用することが出来ます。

ResponseListener listener = new ResponseListener() {
        public void onResponse(ResponseEvent event) {
                ・・・
                Object userHandle = event.getUserObject();
                ・・・
        }
};


実践で使えるか?


非同期処理を利用するとSNMPエージェント毎のタイムアウト、リトライ毎に待機する必要が無くなるので、出来るだけ短時間で多くのSNMPエージェントから情報を収集したい場合には役立ちます。
 
ただ、より高度なレベルを求めると不満もあります。まず、SNMP4Jの提供する非同期処理では非同期の量をコントールできません。メソッドを呼び出すごとにSNMPリクエストを送信します。よって、多くの(例えば10000台の)エージェントにSNMPリクエストを送信する場合、I/Oリソースが許すだけの瞬間的なリクエスト・トラフィックが発生します。

ネットワーク・リソースに余裕があれば全く問題ありませんが、場合によっては不足し、SNMPエージェントがアクティブ(動作している)にも関わらずマネージャから見るとタイムアウトになってしまうことがあります(特にネットワーク経路上にNATルータが存在し、かつ、短時間に大量のSNMPリクエストを送信する場合にルータがボトルネックになる事があります)。
 
ですから、非同期する量もコントロールしたい、と思う事も長い実践の中では出てきます。これはSnmp::get(PDU pdu, Target target, Object userHandle, ResponseListener listener)メソッドを呼び出す間隔をコントロールすることで実現できますが、呼び出し側で都度コーディングするのも面倒です。

僕はSNMP4Jをラッピングする高レベルAPIを自作し、呼出し側からは必要に応じてスレッド数のみを指定できるようにしています。

後は実際にSNMPアプリケーションを開発すると、個別のポーリングは非同期ながら全体の終了については同期したい、と思うこともあるのではないでしょうか。Snmpクラスではここまでは用意されておらず呼出し側で実装する必要があります。
 

入門SNMP
入門SNMP
posted with amazlet at 11.03.25
ダグラス・R. マウロ ケビン・J. シュミット 
オライリー・ジャパン 
売り上げランキング: 59144

増補改訂版Java言語で学ぶデザインパターン入門
結城 浩 
ソフトバンククリエイティブ 
売り上げランキング: 29407

Posted by netbuffalo at 19:07│TrackBack(0)実践SNMP+Java 


この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/netbuffalo/2948281