7.NetMonitor
今回の例は、ネットワークのモニタで、入力と出力のパケット数を表示する。簡単なプログラムだが、Reactive-bananaで記述されるプログラムの基本的な構成をとっている。そこで、今回は、Reactive-bananaの基本的な枠組みを紹介しながら、ネットワークをモニタするプログラムを解説する。
7.1 Reactive-bananaの基本的な枠組み
振舞いやイベントについては、理解が進んできたと思うが、イベントがどのように処理されるのかについては、まだ分かっていないことが多いと思う。
Reactive-bananaを構成するモジュールの一つであるReactive.Banana.Combinatorsでは、入力イベントから出力イベントを得る関数をいくつか用意している。CounterとTwoCountersの記事で出てきたunionsはその一つである。そのほかに、filterE(下図参照), collect, spill, accumE, apply, stepper(下図参照)などがある。このように、イベントからイベント(あるいは振舞い)を得るものを、Reactive-bananaでは、イベント・グラフと呼んでいる。
イベント・グラフに加えて、入力と出力を一緒にしたものをイベント・ネットワークと呼んでいる。イベント・ネットワークを構築するためには、イベント・グラフ、入力、出力をモナドMomentの中で記述する。そして、これらからイベント・ネットワークを利用できるようにするためには関数compileを利用する。さらに、イベント・ネットワークを動かすためには、関数actuateを用いる。これにより、ネットワークは、入力イベントを登録し、出力を開始できるようになる。
典型的なプログラムは次のようになる。下記のプログラムで、emouseからedieまでが入力、letの中のbehavior1からevent15までがイベント・グラフ、二つのreactimateが出力である(これまでの例では、reactimateの代わりにsinkを用いた)。
main = do -- initialize your GUI framework window <- newWindow ... -- describe the event network let networkDescription :: forall t. Frameworks t => Moment t () networkDescription = do -- input: obtain Event from functions that register event handlers emouse <- fromAddHandler $ registerMouseEvent window ekeyboard <- fromAddHandler $ registerKeyEvent window -- input: obtain Behavior from changes btext <- fromChanges "" $ registerTextChange editBox -- input: obtain Behavior from mutable data by polling bdie <- fromPoll $ randomRIO (1,6) -- express event graph let behavior1 = accumB ... ... event15 = union event13 event14 -- output: animate some event occurences reactimate $ fmap print event15 reactimate $ fmap drawCircle eventCircle -- compile network description into a network network <- compile networkDescription -- register handlers and start producing outputs actuate network
このプログラムの、イベントを入力する部分で使われている関数について説明しておこう。ここでは、三種類のものが使われている。一つは、マウスやキーボードなどが押されたことにより生じるイベントである。また、もう一つは、テキストなどのように変化があった時に生じるイベントである。最後は、変化していくデータからのイベントである。次のようになっている。
fromAddHandlerは、イベントハンドラ(上記では、registerMouseEvent, registerKeyEvent)を登録しておくことで、イベント(この場合には、マウスとキーボード)の検出を可能にする(他の言語では、コールバック関数あるいはリスナーと呼ばれている)。
fromChangesは、変化があったときにイベントハンドラ(上記では、registerTextChange )から知らせてもらう。
fromPollは、変更可能なデータ(たとえば現在時刻)を頻繁にポーリングし、振舞いを得る。上記のプログラムでは乱数の発生をポーリングしている。
出力を定義している部分にあるreactimateは、イベントが発生したときはいつでも、IOの処理を行う。なお、次のことに注意しておくことが必要である。reactimateは、二つのイベントがとても短時間のうちに前後して生じたときは、一方の処理が終了してから、次のイベントを処理するというわけにはいかず、二つのイベントの処理がインターリーブになることがある。
7.2 ユーザ・インタフェース
例によって、ユーザ・インタフェースを定義する。送ったパケットの数と受けたそれとを表示する必要があるので、次のようになる。
f <- frame [text := "Network Monitor"] out1 <- staticText f [] out2 <- staticText f [] set f [layout := minsize (sz 250 70) $ margin 10 $ column 10 [label "TCP network statistics", grid 5 5 [[label "Packets sent: ", widget out1] ,[label "Packets received: ", widget out2]] ] ]
初期画面は次のようになる。
7.3 イベント・ネットワークの構築
イベント・ネットワークを構築する前に、タイマーの準備をしておく。これは、ネットワークをモニタするための間隔を与えるタイマーである。ここでは、500ミリ秒ごとにモニタすることにする。
t <- timer f [ interval := 500 ] -- timer every 500 ms
それでは、入力、イベント・グラフ、出力からなるイベント・ネットワークを構築しよう。
入力
入力は、ネットワークの状況を得ることである。これは、後で定義する関数getoNetworkStatisticsから、得ることにすると、入力は次のようになる。
bnetwork <- fromPoll getNetworkStatistics
イベント・グラフ
イベント・グラフは、タイマーからモニタする時間が来たというイベントである。これは次のようになる。
etick <- event0 t command
出力
最後に出力であるが、これはreactimateの代わりにsinkを用いる。これは、出力でGUIのためのウィジェットを利用することによる。次のようになる。
let showSent = maybe "parse error" show . fst showReceived = maybe "parse error" show . snd sink out1 [ text :== showSent <$> bnetwork ] sink out2 [ text :== showReceived <$> bnetwork ]
ネットワークの状況は、システムプロセスのnetstatを用いて次のように得ることができる。
getNetworkStatistics :: IO NetworkStatistics getNetworkStatistics = do s <- readProcess "netstat" ["-s", "-p","tcp"] "" return (readField "packets sent" s ,readField "packets received" s) readField :: String -> String -> Maybe Int readField fieldname = id . fmap (read . filter isDigit) . listToMaybe . filter (fieldname `isSuffixOf`) . lines
後はcompile,actuateして実行すればよいことになる。
7.4 プログラムの全体
プログラムはこのホームページで公表されているが、転載する。
{----------------------------------------------------------------------------- reactive-banana-wx Example: Minuscule network monitor ------------------------------------------------------------------------------} import Data.Char import Data.List import Data.Maybe import System.Process import Graphics.UI.WX hiding (Event) import Reactive.Banana import Reactive.Banana.WX {----------------------------------------------------------------------------- Main ------------------------------------------------------------------------------} main :: IO () main = start $ do f <- frame [text := "Network Monitor"] out1 <- staticText f [] out2 <- staticText f [] set f [layout := minsize (sz 250 70) $ margin 10 $ column 10 [label "TCP network statistics", grid 5 5 [[label "Packets sent: ", widget out1] ,[label "Packets received: ", widget out2]] ] ] t <- timer f [ interval := 500 ] -- timer every 500 ms let networkDescription :: MomentIO () networkDescription = do -- The network statistics are polled when and only when -- the event network handles an event. bnetwork <- fromPoll getNetworkStatistics -- That's why we need a timer that generates regular events to handle. etick <- event0 t command let showSent = maybe "parse error" show . fst showReceived = maybe "parse error" show . snd sink out1 [ text :== showSent <$> bnetwork ] sink out2 [ text :== showReceived <$> bnetwork ] network <- compile networkDescription actuate network -- Obtain network statistics from the netstat utility type NetworkStatistics = (Maybe Int, Maybe Int) getNetworkStatistics :: IO NetworkStatistics getNetworkStatistics = do s <- readProcess "netstat" ["-s", "-p","tcp"] "" return (readField "packets sent" s ,readField "packets received" s) readField :: String -> String -> Maybe Int readField fieldname = id . fmap (read . filter isDigit) . listToMaybe . filter (fieldname `isSuffixOf`) . lines