24
|
1 \chapter{画面共有システムTreeVNCの設計}
|
|
2
|
|
3 \section{木構造を用いたTreeVNCの設計}
|
|
4 まず、多人数が参加している授業でVNCを使う場合に起こる問題は、一つのコンピュータに多人数が繋がり、処理が集中してしまって、性能が大幅に落ちてしまうところである(図\ref{fig:vnc})。
|
|
5 多人数の同時接続を可能にするには、一極集中で接続するのではなく、Node同士で負荷を分散させることによって実現できるのではないかと考えた。\\
|
|
6 負荷分散を行う際、Node同士どのようなトポロジを組むのが適切か検討した結果、上から流れてきたデータを下のNodeへと伝えていくことのできる木構造が良いと考えた。\\
|
|
7 今回行った設計ではNodeを木構造に接続させデータを流すためにサーバとNodeの間にRoot Node(サーバとNodeの通信を仲介するもの)を設置する方式をとった。Root Nodeは主にNodeの管理とServerから流れてきた画像データの管理を担当する。\\
|
|
8 木構造で設計したものを(図\ref{fig:treestructure})に示す。\\
|
|
9
|
|
10
|
|
11 \begin{figure}[htbp]
|
|
12 \begin{minipage}{0.5\hsize}
|
|
13 \begin{center}
|
|
14 \includegraphics[width=80mm]{./images/vnc.pdf}
|
|
15 \end{center}
|
|
16 \caption{VNCの構造}
|
|
17 \label{fig:vnc}
|
|
18 \end{minipage}
|
|
19 \begin{minipage}{0.5\hsize}
|
|
20 \begin{center}
|
|
21 \includegraphics[width=80mm]{./images/treestructure.pdf}
|
|
22 \end{center}
|
|
23 \caption{TreeVNCの構造}
|
|
24 \label{fig:treestructure}
|
|
25 \end{minipage}
|
|
26 \end{figure}
|
|
27
|
|
28 \newpage
|
|
29
|
|
30 \section{TreeVNCの原理}
|
|
31 TreeVNCがどのような負荷分散を行った結果、どのように負荷が分散されているのかを説明する。\\
|
|
32 通常のVNCでは、一極集中型でサーバに接続してしまうのでサーバに負荷がかかって性能を低下させたり停止してしまっている。そこでNodeを木構造に配置させることで、負荷がなくなっているように見える。しかし、実は負荷がスイッチにかかっていて、消えているわけではない。
|
|
33 通常のVNCとTreeVNCの構造を比較した図を(図\ref{fig:comparenormalandtree})に示す。\\
|
|
34
|
|
35 \begin{figure}[!htbp]
|
|
36 \begin{center}
|
|
37 \includegraphics[width=130mm]{./images/comparenormalandtree.pdf}
|
|
38 \end{center}
|
|
39 \caption{TreeVNCの構造}
|
|
40 \label{fig:comparenormalandtree}
|
|
41 \end{figure}
|
|
42
|
|
43
|
|
44 (表\ref{tb:oneporttraffic})はポート一本あたりの通信量である。\\
|
|
45 表から推察できるように、ポート一本あたりの負荷は通常のVNCの場合はNode数に比例して増えている。しかしTreeVNCの場合はTreeの子供の数が一定なので、Node数に関係なく一定である。\\
|
|
46 送信する量も通常のVNCの場合Node数に比例した量のデータ送信しなければならいので、CPUに負荷がかかり性能が低下したり停止したりしている。\\
|
|
47 対してTreeVNCはが増えても配信するデータは一定なので性能が低下せず使用することができる。
|
|
48
|
|
49 \begin{table}[htbp]
|
|
50 \caption{ポート一本あたりの通信量(NはNode数、MはTreeの子供の数)}
|
|
51 \label{tb:oneporttraffic}
|
|
52 \begin{center}
|
|
53 \begin{tabular}{|c|c|c|} \hline
|
|
54 & 通常のVNC & TreeVNC \\ \hline
|
|
55 通信量 & N * データ量 & (M + 1) * データ量 \\ \hline
|
|
56 \end{tabular}
|
|
57 \end{center}
|
|
58 \end{table}
|
|
59
|
|
60
|
|
61
|
|
62 %\section{画面拡大縮小}
|
|
63 %\section{画面描画範囲の指定}
|
|
64
|
|
65 \subsection{木の生成}
|
|
66 負荷を分散させるために木構造を用いるので、Nodeをツリー状に接続する仕組みが必要である。TreeVNCでは、以下の点順で、木の構成を行う。\\
|
|
67 図\ref{fig:createtree}は、2分木で木を構成する際の手続きを示したシーケンスダイアグラムである。\\
|
|
68 1.初めにNodeはRoot Nodeに接続先IPを尋ねる(Where connect)。\\
|
|
69 2.Root Nodeは、Nodeに接続するべきホストのIPを引き渡す(Answer)。\\
|
|
70 3.Nodeは指定されたホストに接続する(Connect)。
|
|
71
|
|
72 \begin{figure}[!htbp]
|
|
73 \begin{center}
|
|
74 \includegraphics[width=140mm]{./images/createtree.pdf}
|
|
75 \end{center}
|
|
76 \caption{TreeVNCの構造}
|
|
77 \label{fig:createtree}
|
|
78 \end{figure}
|
|
79 \newpage
|
|
80
|
|
81
|
|
82 \subsection{Root Nodeの仕事}
|
|
83 Root Nodeの大きな仕事は、Nodeの管理である。NodeのIPアドレス情報をJavaのLinkedListで保持している。
|
|
84 LinkedListの簡単な説明を\ref{tb:LinkedList}に示す。
|
|
85
|
|
86 \begin{table}[htbp]
|
|
87 \caption{LinkedList}
|
|
88 \label{tb:LinkedList}
|
|
89 \begin{center}
|
|
90 \begin{tabular}{|c||c|} \hline
|
|
91 関数 & 説明 \\ \hline
|
|
92 add(E e) & Listの最後にeを追加する。 \\ \hline
|
|
93 get(int index) & リストの$index$番目の値を取得する。\\ \hline
|
|
94 remove(int index) & リストの$index$番目の値を削除する。 \\ \hline
|
|
95 \end{tabular}
|
|
96 \end{center}
|
|
97 \end{table}
|
|
98
|
|
99
|
|
100 Listing\ref{src:tree}はNodeを管理している部分のプログラムである。
|
|
101 %$line$は接続先を訪ねてきたNodeのIPアドレスであり、$(nodeCounter - 1) / treebranch$で接続するべき親を求め結果を返している。\\
|
|
102 %treebranchは木の分岐数である。
|
|
103
|
|
104 $line$は接続先を訪ねてきたNodeのIPアドレスである。初めに接続してきた、Nodeのアドレスを自分が保持しているlist(LinkedList)に登録する。\\
|
|
105 親の番号は$(counter - 1)/TreeBraanche$で求めることができるので、listに親の番号を指定し、親のIPアドレスを取得する。\\
|
|
106 取得したIpを接続してきたNode($line$)に送り返すことで、NodeはどのIPアドレスに接続すればよいのか知ることができる。
|
|
107
|
|
108 \begin{lstlisting}[language=java,frame=lrbt,label=src:tree,caption=クライアント管理のプログラム,numbers=left]
|
|
109 private LinkedList<String> ls = new LinkedList()<String>;
|
|
110 private synchronized void replyCreateTree(PrintStream os, String line) throws InterruptedException {
|
|
111 ls.add(line);
|
|
112 parentnum = (nodeCounter - 1) / treebranch;
|
|
113 request = ls.get(parentnum);
|
|
114 outputStream(os, request, String.valueOf(parentnum), String.valueOf(nodeCounter));
|
|
115 }
|
|
116 \end{lstlisting}
|
|
117
|
|
118
|
|
119 \newpage
|
|
120 \section{表示画面の切り替え}
|
|
121 VNCを使用して画面共有を行う場合、授業などでは講師の画面を共有していれば問題ない。しかし、ゼミなどの発表者が多数いる場合は画面共有の対象をを切り替えなければならない。画面共有対象の切り替えを行う場合、発表者ごとにサーバを立ち上げ直さなければならないという問題が発生する。そこで、ユーザ側からRoot Nodeにリクエストを出して、画面共有の対象を切り替える機能が必要になる。
|
|
122 画面を切り替えるときは、Root Nodeだけが接続を切り替え、Nodeたちはそのデータを受け取るだけで良い(図\ref{fig:change})。
|
|
123
|
|
124 \begin{figure}[!htbp]
|
|
125 \begin{center}
|
|
126 \includegraphics[width=100mm]{./images/changeserver.pdf}
|
|
127 \end{center}
|
|
128 \caption{表示画面の切り替え}
|
|
129 \label{fig:change}
|
|
130 \end{figure}
|
|
131 画面の切替をどのユーザが行うのかという問題がある。Root Nodeに対してユーザが毎回IPアドレスを入力して切り替えるのは手間がかかる。そこで、Node側に画面切り替えボタンを設置し、ボタンを押すとRoot Nodeに自分の画面へ切り替えるように命令を出しRoot Nodeが了承すると画面が切り替わるように設計した。
|
|
132
|
|
133 \newpage
|
|
134 \section{マルチディスプレイの対応}
|
|
135 マルチディスプレイを用いてVNCを行うと、すべてのディスプレイのデータが繋がって表示されてしまう。通常発表などに使用されるディスプレイは一つである。すべての画面のデータを送ってしまうとその分だけ無駄なデータを送っていることになる。そこで、発表用に使用する画面のデータだけを送ることのできるようにディスプレイが指定できるようになることが必要になる。
|
|
136
|
|
137
|
|
138 \section{再接続}
|
|
139 木を構成することはできたが途中のNodeが切断してしまった場合に木を再構成しなければならない。
|
|
140 木を再構成する手順は以下の用に行う。
|
|
141 \begin{enumerate}
|
|
142 \item 子供のリーダー(最初に親につないだ子供)が親が落ちたことをRoot Nodeに対して報告する。
|
|
143 \item Root Nodeは報告を受けると番号の一番大きいNode(最後のNode)に対して落ちた親の代\
|
|
144 わりになるように報告する。
|
|
145 \item Root Nodeから命令を受けたNodeは指定されたNodeに接続をしなおす。
|
|
146 \item Root NodeはNodeのリストを更新して、親が落ちた子供たちに新しい親の情報を教える。
|
|
147 \item 親が切断された子供たちは、Root Nodeからもらった情報を元に新しい親に対して接続を行う。
|
|
148 \end{enumerate}
|
|
149 このようにして木を再構成することができる。
|
|
150 \newpage
|
|
151
|
|
152 図\ref{fig:reconnection}は再接続の様子を記したコラボレーションダイアグラムである。以下に関数の説明をする。
|
|
153
|
|
154 \begin{figure}[!htbp]
|
|
155 \begin{center}
|
|
156 \includegraphics[width=140mm]{./images/reconnection.pdf}
|
|
157 \end{center}
|
|
158 \caption{再接続の手続き}
|
|
159 \label{fig:reconnection}
|
|
160 \end{figure}
|
|
161
|
|
162 1:lostHost():親に切断されたこと報告する関数である。\\
|
|
163
|
|
164 2:reportLastNode():番号の一番大きい(最後のノード)に対して親の代わりをするように命令する関数である。\\
|
|
165
|
|
166 3:listUpdate():Root Nodeが持つNodeのリスト情報をアップデートする(切断したノードを削除し最後のノードのアドレスをそこに追加する)。\\
|
|
167
|
|
168 4:waitReply.start():NodeはwaitReplyというクラスをmainスレッドとは別にスレッドを作成して走らせている。
|
|
169 もしRoot Nodeからの命令が来るとNodeはRoot Nodeから指定された場所に接続を行う。\\
|
|
170
|
|
171 \newpage
|
|
172
|
|
173
|
|
174 \begin{figure}[!htbp]
|
|
175 \begin{center}
|
|
176 \includegraphics[width=140mm]{./images/reconnection2.pdf}
|
|
177 \end{center}
|
|
178 \caption{再接続の手続き2}
|
|
179 \label{fig:reconnection2}
|
|
180 \end{figure}
|
|
181
|
|
182 6:replyChildlen()は、親が切断した子供たちに対して新しい親の情報を報告する関数である。\\
|
|
183
|
|
184 7:reConnection()はRoot Nodeから来た情報をもとにVNC接続を行う関数である。
|
|
185
|
|
186 以上の関数を用いることでNodeが落ちても木を再構成することができる。
|
|
187
|
|
188 \newpage
|
|
189
|
|
190 \section{MulticastQueue}
|
|
191 画面が更新された際に画像の更新をNodeに伝えなければならない。ノードが多数ある場合、各ノードに順番に更新を知らせるのではなく、同時に画面の更新を知らせたい。
|
|
192 同時に更新を知らせるために、CountDownLatchを用いてMultiCastQueueを作成した。
|
|
193
|
|
194 \begin{figure}[!htbp]
|
|
195 \begin{center}
|
|
196 \includegraphics[width=100mm]{./images/countdownlatch.pdf}
|
|
197 \end{center}
|
|
198 \caption{CountDownLatch}
|
|
199 \label{fig:CountDownLatch}
|
|
200 \end{figure}
|
|
201
|
|
202
|
|
203 java.util.concurrent.CountDownLatchはjavaの並列用に用意されたAPIで他のスレッドで実行中の操作が完了するまで、複数のスレッドを待機させることのできるクラスである。
|
|
204 使い方は、カウント(何回カウントしたらスレッドを開放するのかの数)を指定してCountDownLatchの初期化を行う。
|
|
205 countDownメソッドを使うとカウントダウンを行うことができる。
|
|
206 awaitメソッドはcowntDownメソッドの呼び出しの結果カウントがゼロになるまでの間スレッドをブロックすることができる。
|
|
207 countDwonメソッドの結果がゼロになるとawaitメソッドで停止していたスレッドが動き出す(図\ref{fig:CountDownLatch})。
|
|
208 MulticastQueueはQueueのように使用することができる。
|
|
209 putメソッドを使用してデータをqueueに追加する。データをputする際にCountDownLatchをカウントダウンする。
|
|
210 pollメソッドを用いることで、次のデータを取得することができる。
|
|
211 pollメソッドの中でawaitが使われているので次のputでデータが来るまでスレッドがブロックする。(図\ref{fig:multicastqueue})
|
|
212
|
|
213 新しくデータがputされるとデータの読み込みが再開される。(図\ref{fig:multicastqueue2})
|
|
214
|
|
215 \begin{figure}[!htbp]
|
|
216 \begin{center}
|
|
217 \includegraphics[width=120mm]{./images/multicastqueue.pdf}
|
|
218 \end{center}
|
|
219 \caption{MulticastQueue(データが来るまで待つ)}
|
|
220 \label{fig:multicastqueue}
|
|
221 \end{figure}
|
|
222
|
|
223 \begin{figure}[!htbp]
|
|
224 \begin{center}
|
|
225 \includegraphics[width=120mm]{./images/multicastqueue2.pdf}
|
|
226 \end{center}
|
|
227 \caption{MulticastQueue(新しいデータが来るとデータを読み出す)}
|
|
228 \label{fig:multicastqueue2}
|
|
229 \end{figure}
|
|
230
|
|
231 \newpage
|
|
232 \subsection{TimeOut}
|
|
233 MultiCastQueueを使ってのデータの取得には問題が発生した。
|
|
234 それは、接続してきたNodeがデータを取得しない状況、例えばサスペンド状態になったときにRoot Nodeのメモリの中にデータが残り続けるというものである。
|
|
235 メモリに残り続けたデータはやがてメモリオーバーフローを引き起こしてしまうのである。その様子を図\ref{fig:TimeOut}に示す。
|
|
236 TimeOutスレッドがNodeの代わりにデータを取得することで、MulticastQueueの中からデータが削除されRoot Nodeのメモリを圧迫することがなくなった。(図\ref{fig:TimeOut2})
|
|
237
|
|
238 \begin{figure}[tb]
|
|
239 \begin{center}
|
|
240 \includegraphics[scale = 0.5]{images/TimeOut.pdf}
|
|
241 \end{center}
|
|
242 \caption{
|
|
243 Nodeサスペンド時のRoot Nodeのメモリの様子。
|
|
244 データが残り続けメモリを圧迫してしまう。
|
|
245 }
|
|
246 \label{fig:TimeOut}
|
|
247 \end{figure}
|
|
248
|
|
249
|
|
250 そこで、ある一定の時間が経過すると代わりにデータを取得してくれるTimeOut用のスレッドを作成した。
|
|
251 TimeOutスレッドはサスペンドしているNodeの代わりにデータを取得する。
|
|
252
|
|
253 \begin{figure}[tb]
|
|
254 \begin{center}
|
|
255 \includegraphics[scale = 0.5]{images/TimeOut2.pdf}
|
|
256 \end{center}
|
|
257 \caption{
|
|
258 TimeOutが代わりにデータを取得する
|
|
259 }
|
|
260 \label{fig:TimeOut2}
|
|
261 \end{figure}
|
|
262
|
|
263
|
|
264
|
|
265
|
|
266
|
|
267
|
|
268 \section{圧縮の問題}
|
|
269 VNCで扱うRFB プロトコルには、使えるエンコーディングのタイプの1つとしてZRLE(Zlib Run-Length Encoding)がある。
|
|
270 ZRLEはZlibで圧縮されたデータとそのデータのバイト数がヘッダーとして付けられ送られてくる。
|
|
271 Zlibはフリーのデータ圧縮及び解凍を行うライブラリである。
|
|
272 可逆圧縮アルゴリズムの圧縮と解凍が行えるjava.util.zip.deflaterとjava.util.zip.inflaterを実装している。
|
|
273
|
|
274 \subsection{java.util.zip.deflaterの実装の問題}
|
|
275 Zlib圧縮は辞書を持っていて、その辞書に登録されているデータを元に解凍が行われる(図\ref{fig:ZRLE})。
|
|
276 しかし、java.util.zip.deflaterは現在持っている辞書を書き出すこと(flush)ができないことが分かった。
|
|
277 辞書を書きだすことができない為、Zlib圧縮されたデータを途中から受け取ってもデータが正しく解凍を行うことができない
|
|
278 (図\ref{fig:ZRLE2})。
|
|
279 %元々のZlibの規約にはこの辞書をflushする機能があったがJavaには実装されていなかった\\
|
|
280
|
|
281
|
|
282 \begin{figure}[!htbp]
|
|
283 \begin{center}
|
|
284 \includegraphics[width=100mm]{images/ZRLE.pdf}
|
|
285 \end{center}
|
|
286 \caption{
|
|
287 ZRLE
|
|
288 }
|
|
289 \label{fig:ZRLE}
|
|
290 \end{figure}
|
|
291
|
|
292
|
|
293 \begin{figure}[!htbp]
|
|
294 \begin{center}
|
|
295 \includegraphics[width=100mm]{images/ZRLE2.pdf}
|
|
296 \end{center}
|
|
297 \caption{
|
|
298 ZRLE2
|
|
299 }
|
|
300 \label{fig:ZRLE2}
|
|
301 \end{figure}
|
|
302
|
|
303
|
|
304 \subsection{ZRLEE}
|
|
305 そこで、Root NodeがZRLEで受け取ったデータをunzipし、データをzipし直して最後にfinish()
|
|
306 をいれることで初めからデータを読んでいなくても解凍を行えるようにした
|
|
307 (毎回新しい辞書を使うようにした)。(図\ref{fig:ZRLEE})
|
|
308 このエンコードはZRLEEエンコードと定義した。
|
|
309 一度ZRLEEエンコードに変換してしまえば、そのデータをそのまま流すだけで良い。
|
|
310 よって変換はRoot Nodeが行う一回だけですむ。
|
|
311 ただし、deflater,inflaterでは前回までの通信で得た辞書をクリアしないといけないため、
|
|
312 Root NodeとNode側では毎回新しく作る必要がある(Node側はinflaterだけ)。
|
|
313 また、ZRLEEはNode側が対応していなければならないという問題がある。
|
|
314
|
|
315 \begin{figure}[!htbp]
|
|
316 \begin{center}
|
|
317 \includegraphics[width=120mm]{images/ZRLEE.pdf}
|
|
318 \end{center}
|
|
319 \caption{
|
|
320 ZRLEE
|
|
321 }
|
|
322 \label{fig:ZRLEE}
|
|
323 \end{figure}
|
|
324
|
|
325
|
|
326 \subsection{接続先自動検索システム}
|
|
327 NodeがRoot Nodeに接続する際、Root NodeのIPアドレスを指定する必要がある。IPアドレスを毎回入力するのは手間がかかる上、間違うと接続することができない。\\
|
|
328 そこで、Nodeが起動した際に、起動しているTreeVNCのRoot Nodeを検索し、IPアドレスの情報を取得し、一覧にして選択させることによって直接アドレスを手入力する必要がなくなる。\\
|
|
329 TreeVNCのRoot Nodeを検索する際には、Broadcast通信を用いることによって実現することができる。\\
|
|
330
|
|
331
|
|
332
|
|
333
|
|
334
|
|
335
|
|
336
|
|
337
|