Access,VB5,Jetデータベースエンジンを利用したC/Sシステム構築に関する考察,実験および実践
ファイルにアクセスするか,データベースにアクセスするか,
それがWebサーバーとデータベースサーバーとの違いである
秋月 巌 AKIZUKI,Iwao
| プログラミングによる表現 |
|---|
言語に興味がある.言語そのものが面白いのではなく,言語を用いて何かを表現することが興味深いのである.
海外には外国語を非常に上手く使う日本人がいる.そういう人達の中には外国語を操るということ自体に興味を持つ人がいる.スペイン語は動詞が120種類以上に活用するので,複雑な動詞の活用を使った構文をうまく使えた場合などは確かに爽快な気分である.
しかし,そういったエクセサイズな側面は言語がもつ魅力のひとつにしか過ぎない.プログラミング言語においても,美しく記述することにも意味があるが,それによって何を表現したかの方が大切だろう.
| 人間とコンパイラの違い |
|---|
何かの対象があり,それを言語化していく作業というのは,文学もコンピュータプログラミングも同様である.具体的なイメージを一旦言語化(エンコード)してから,再度,具体化(デコード)する.文学とコンピュータの最大の違いは,文学はデコードの作業を人間が行なうのに対して,コンピュータの場合は機械が行なうことである.
オブジェクト指向が台頭した時期に,オブジェクト指向文学というのが成立するのではないかと考えたことがある.このアイデアはいまだに捨てたわけではないが,人間の頭にはコンパイラが付属していないという理由で難しいと考えている.ロールプレイングゲームは,ある種のオブジェクト指向文学だということができると思うが,これもやはりコンパイラの力があってこそ実現が可能となる.考えてみれば文章を読むということは,コンパイルをしながら処理を実行する行為に近い.まさにインタープリタである.インタープリタのオブジェクト指向プログラミングは理論的には可能だが,処理の流れは著しく非直線的である.たとえば,ある処理の流れの中でボタンオブジェクトが現れたとする.コンパイラは今までの処理を中断し,ボタンを描画する処理の実行をはじめる.このような断続的な流れは,あまり人間の思考システムには適さない.もっとも,人間の脳自体はパラレル処理が可能である.同時に進行する複数の事象に対して,並行して対応できる.たとえば食事をしながらテレビをみるというふうに….しかし,新聞を読みながら同時に雑誌を読むということは難しい.これは人間が言語を理解する際にはシリアライズする必要があることの証明だろう.
| ハイパーリンク,オブジェクト,そして人間 |
|---|
ハイパーリンクは,シリアライズの概念を超越する文学表現である.文章を読んでいる途中で現れた新しい名詞に対して,リンクをクリックすれば関連内容を簡単に表示することができる.しかしWebでインターネットを巡回してリンクをたどっていくうちに,いつも迷子になってしまう私のような人も少なくないはずだ.
同様に,以前のストリームをベースにしたプログラミングに比較して,オブジェクトやコンポーネントをベースとした最近のWindowsプログラミングは,人間にとって迷いこみやすい,迷宮のように思える.
| Visual Basicでプログラミングするとき,最初に理解するべきこと… |
|---|
文化オリエント社の矢澤氏が,「Visual Basicを使ってプログラミングをする場合に,最初に理解するべきことは何か?」と問いかけてきたことがある.妥当に答えるならば,ActiveXコントロールなどのCOMコンポーネントと言語の対応や,イベントドリブンプログラミングの動作メカニズムだ,ということになるだろう.その問いに私は「文字列の操作」と答えておいたのだが,釈然としないものが残った.ご存知のように,Visual Basicは文字列の操作能力が貧弱なうえに,UNICODE特有の問題も持っているからである.「文字列の操作」がプログラミングの基本だとするならば,Visual Basicは基本的に貧弱な言語だということになってしまう.矢澤氏も,この問いについて考え続けていたようで,SDKを使用してVisual Basicで「Hello World」を記述するプログラムを,メールで送ってきた.このプログラムはActiveXコントロールを使うことなく,フォームに文字列を出力している.もちろん,このサンプルには実用的な意味はない.しかし,オブジェクトレスのプログラミングがVisual Basicでもできる,という可能性を示唆している.
| Socketプログラミングは文字列の操作が中心 |
|---|
さて,先月号ではナルボ社の「Easy Socketコントロール」を使用してサーバーとクライアントで通信する方法について説明した.今月のサンプルファイルの中にもEasy Socketコントロールのインストーラを収納しておくが,本誌付録のCD-ROMに収録されていた同社の「Communication Collection」のデモ版をお持ちであれば,そちらを利用した方がよい.
ここではSocket通信を行なうためにこのコントロールを使用しているが,Socketプログラミングそのものは,文字列の操作が中心になっていることを理解しておいてほしい.
| マルチセッション |
|---|
サードパーティ製のコントロールを使用しているのは,Visual Basicに付属のコントロールがマルチセッションに対応していないからである.マルチセッションに対応しているSocketコントロールとして,前述のEasy Socketコントロールの他にも,NextCom社のDistinctがある.ただDistinctは,トライアル版を使った範囲では安心して使える品質の高い製品だが,再配布時にライセンス料がかかるのが気になる点だ.
マルチセッションに対応していないコントロールでサーバーアプリケーションを作成するには厳しいものがある.マルチセッションに対応していなくても,コントロールを配列化してマルチセッションを実現する方法もあるが,これはあくまでも便宜的な方法だということができる.
| Visual Basicとマルチスレッド |
|---|
もっとも,いくらコントロールがマルチセッションに対応していても,Visual Basicはマルチスレッドコンテナをサポートしていないので,サーバーアプリケーションを作成することには無理がある.シングルプロセスで複数のクライアントを動作させる場合,プログラム側で非同期の対応をしないと全部のクライアントが同期して動作してしまう.この問題に対応するために,マルチプロセスでサーバーアプリケーションを動作させることを考えている.ひとつのクライアントに対して,ひとつのプロセスを起動するのである.この方法は接続あたりのコストは高くつくが,プロセス空間によって処理が隔離されるので,安定性は確保できる.現在のようにメモリが低価格だからできる発想だ.Microsoft SQL Serverのクライアントライセンスを1クライアント買う金額(約2万円)で,メモリを64MB以上増設できるのである.
| Webサーバーを自作する |
|---|
先月号ではテキストを送信するプログラムをサンプルとして作成したが,その解説をする前に,Socketを利用して自作したWebサーバーを使って,クライアント/サーバー(以下C/S)システムのメカニズムについて復習しておこう.とはいってもこのWebサーバーは,イメージファイル等のバイナリファイルを扱えない.それでもインターネット標準のアーキテクチャにのっとって動作する姿は一人前である.Webシステムは典型的なC/S形式のシステムなので,概要を理解するのに適している.
| Webサーバーサンプルの概要 |
|---|
図1:Webサーバーサンプル(リクエストを受け付けた状態)![]() |
図1が自作したWebサーバーが動作している様子である.下側のテキストボックスにはブラウザが送信したリクエストが表示される.ホームディレクトリ項目に指定したパスが,Webのホームディレクトリである.Cドライブのルート以外をホームディレクトリとして設定したい場合は,パスをテキストボックスに入力する.
図2はデフォルトの設定(C:\)で,Cドライブのルートディレクトリにあるindex.htm(リスト1)を,ブラウザで参照した結果の画面である.
このサンプルを動作させる場合,実行するコンピュータでWebサーバーが起動している場合は,サービスを停止する必要がある.エラーハンドラを指定していないので,指定したファイル等がまちがっていた場合は,サーバープログラムの実行が終了してしまうので注意する必要がある.
図2はデフォルトの設定(C:\)で,Cドライブのルートディレクトリにあるindex.htm(リスト1)を,ブラウザで参照した結果の画面である.
このサンプルを動作させる場合,実行するコンピュータでWebサーバーが起動している場合は,サービスを停止する必要がある.エラーハンドラを指定していないので,指定したファイル等がまちがっていた場合は,サーバープログラムの実行が終了してしまうので注意する必要がある.

リスト1:index.htm
<HTML> <BODY> <H1> デモページ</H1> </BODY> </HTML> |
| Webサーバープログラムの動作 |
|---|
リスト2がWebサーバーを構成するすべてのソースコードである.行なっている処理は驚くほど簡単である.
ReceiveTextイベントプロシージャの引数pszTextには,ブラウザからのリクエストデータがわたされる.ブラウザが送信した内容が,図1の画面に表示されている文字列である.最初の「GET /index.htm」という部分に注目してほしい.ブラウザが要求しているのがホームディレクトリにあるindex.htmというファイルだということである.
要求はインターネットのURLの表記に従って,ディレクトリ名がスラッシュ(/)で区切られるので,ファイルのサーチパスを取得するためには,「/」を「\」に置き換える必要がある.次のコードはリスト下部で定義されているユーザー定義関数によって,文字を置き換える.
reqFile = replStr(reqFile, "/", "\")
たとえば指定されたリクエストが「/abc/xyz.htm」だとすれば,変数reqFileには文字列「abc\xyz.htm」が格納される.この文字列とホームディレクトリを指定するテキストボックスの値を連結すれば,ファイルのフルパスが特定できる.たとえば,ホームディレクトリに「C:\」と指定されていれば「C:\abc\xyz.htm」がターゲットファイルだということになる.
次のコードはターゲットファイルをオープンするためのコードである.
Open textHome.Text + reqFile For Input As #1
Openステートメントを利用してファイルを開き,以下のコードによってファイルの内容を変数sendHTMLにロードする.
Do While Not EOF(1) 'ファイルの読み出し Line Input #1, a$ sendHTML = sendHTML & a$ & vbCrLf Loop
読みとった内容をブラウザに返信しているのが,次の1行のコードである.引数nIndexはリクエストを送ったSocketsコレクションのIDなので,マルチセッションによって複数のクライアントと同時に通信を行なっている場合でも,リクエストを要求したユーザーに対して応答ができる.
KnSocket1.Sockets(nIndex).SendText sendHTML 'ファイルの内容を返信
次のコードでSocketを破棄して再生成しているのは,実は問題がある.
KnSocket1.Close KnSocket1.Open
HTTPのセッションでは,リクエストの終了後もブラウザは接続を遮断してこないため,サーバー側で切断する必要がある.サーバーが切断しないとブラウザは追加の返信があるものと判断し,いつまでも,受信状態で待機する.ブラウザのツールバー右側,IEのアニメーションがいつまでも動いている状態である.
そのため,セッションが終了したことを通知するため,サーバー側でコネクションを切断するのだが,このコードのようにCloseメソッドを使用した場合には,マルチセッションを張っていてもすべてのコネクションを切断ししまう.これはEasy Socketコントロールの機能の制限からくる限界である.つまり,Easy Socketコントロールを使用して通信を行なう場合は,常にクライアント側からコネクションを切断する必要があるということだ.
Private Sub Form_Load()
KnSocket1.Open
End Sub
Private Sub KnSocket1_ReceiveText (ByVal nIndex As Integer, ByVal pszText As String)
Dim endFilePoint As Integer
Dim reqFile As String
Dim sendHTML As String
Text1 = pszText 'ブラウザからのリクエストをテキストボックスに表示
endFilePoint = InStr(5, pszText, " ") - 6
reqFile = Mid(pszText, 6, endFilePoint) '要求されたファイル名を切り出す
sendHTML = ""
reqFile = replStr(reqFile, "/", "\") 'パス名を作成するため / を \に変換
Open textHome.Text + reqFile For Input As #1 'ファイルのオープン
Do While Not EOF(1) 'ファイルの読み出し
Line Input #1, a$
sendHTML = sendHTML & a$ & vbCrLf
Loop
Close #1
DoEvents
KnSocket1.Sockets(nIndex).SendText sendHTML 'ファイルの内容を返信
KnSocket1.Close
KnSocket1.Open
End Sub
'文字列を置き換えるための関数(/を\に置換する)
Function replStr(replace_string As String, orgStr As String, newStr As String)
search_start = 1
Do While search_start <> 0
search_start = InStr(search_start, replace_string, orgStr, 1)
If search_start <> 0 Then
Mid(replace_string, search_start, 1) = newStr
End If
b Loop
replStr = replace_string
End Function
|
| WebサーバーもC/Sシステム |
|---|
マルチセッションを維持したサーバーが複数のクライアントからの要求に対し,それぞれに応答する方法をWebサーバーサンプルを使って説明した.これによりWebサーバーというものが,C/Sスタイルのファイル共有サーバーであることがわかったと思う.
ここで,少し発想を豊かにしていただきたい.Webサーバーサンプルのソースコードでファイルを読み込んでいる部分に,ファイル操作の代わりにデータベースアクセス処理を記述したらどうなるだろうか.このサンプルプログラムは,Webサーバーでなくデータベースサーバーになるのである.専用クライアントを作成すれば普通のC/Sシステムであるし,Webブラウザを使えば,そのまま,今はやりのWeb-DB連携システムになる.クライアントプログラムや,クライアントのODBC設定すら必要ない,TCOが抜群に低いデータベースシステムである.もちろん,Visual BasicでWeb-DBシステムを開発するなら,Active Server Pagesを用いる方法があり,その方が多くの点で現実的である.特にブラウザをクライアントの対象とする場合,Easy Socketコントロールのマルチセッション中,単一のSocketを破棄できないというのは致命的である.しかし,Active Server Pagesも内部的には,同様の処理をしているのだということを理解していただきたい.
| DBサーバーType1 |
|---|
ここで,先月,紹介したデータベースサーバーの解説に戻る.このサーバーアプリケーションをDBサーバーType1と命名することにしよう. DBサーバーType1は,前出のWebサーバーサンプルを専用クライアントに限定し,ファイルサービスをデータベースサービスに変更したものだということができる.プロトコルにHTTPを使用するわけではないので,ポート番号に1001番を使用している.特徴として次のようなことが挙げられる.
| 現時点での問題点 |
|---|
上に挙げたうちの3,4はネガティブな特徴である.通信のためにバリアント配列を利用するのは,プロトコルの設計をシンプルにする上で有利な条件のつもりだったのだが,性能的に問題が出てしまった.プログラミングが多少冗長になっても,ストリングストリームを使用するべきだろう.シングルスレッドで動作する問題についても,まだ未対応である.シングルスレッドで動作するということは,マルチユーザーで稼動した場合,先にデータベースアクセスを実行したユーザーの処理が終了するまで,次のユーザーの処理はキューイングされるということである.
これに対しては前述したように,ActiveX EXEを用いてマルチプロセスのマルチタスク化をはかる予定である.Visual Basicは原則的にシングルプロセスのマルチスレッドコンテナに対応していないので,マルチタスク処理をするためには別プロセスを起動する方が有効である.また,Jetデータベースエンジンのような,シングルプロセスに対してひとつのインスタンスを使用するように設計されているエンジンの場合,専用のプロセスを使用した方が間違いない.『Jetデータベースエンジンプログラマーズガイド改訂新版』(Microsoft Press/アスキー刊)で調べてみると,Jetデータベースエンジンをシングルプロセス内で複数起動することについて,あるページでは可能となっているが,別のページでは不可能と記述してある.ただ,Active Server Pagesを用いてJetデータベースエンジンを利用したときに,Jetデータベースエンジンはひとつのプロセスに複数ロードされている.そのことを考えれば,複数インスタンスを1プロセスで使用できないということはないはずである.
| 今後は,ActiveX EXEで真のマルチユーザー対応に… |
|---|
クライアントからの接続要求を受けると,サーバーはユーザーの数と同じだけのSocketを生成する.各Socketに対して,別個のActiveX.exeを起動してデータベースエシジン(ODBCドライバ)をロードし,共通のMDBファイルにアクセスするという構成である.つまり,ファイル共有のマルチユーザーデータベースアクセスを1台のマシンで動作させ,結果だけを複数のマシンに分配する.ActiveX EXEは起動コストが大きいが,ユーザーがログインする最初の1回だけなので,問題はないだろう.また,アウトオブプロセスで動作するコンポーネントのマーシャリング(プロセス間通信)のオーバーヘッドの大きさを指摘する人もいるかもしれないが,このオーバーヘッドは1/1000オーダーである.オーバーヘッドは各リクエスト毎に発生するが,特に大きなデータ通信,つまり,大きな結果セットのやり取りを行なわない限りは実用の範囲に入るはずである.それ以前に巨大な結果セットを通信することは,ネットワークトラフィックの面からも問題がある.しかし「…なはず…」が「つかいものにならなかった…」に変わるのが,現実のWindowsプログラミングである.前述のバリアント配列の問題も同様だった.このあたりの試行錯誤の過程もできる限りレポートしてゆきたい.
| DBサーバーType1使用法 |
|---|
とにかく,今回はまだ,Type1である.実用的なデータベースサーバーにはほど遠くても仕方がない.それでは先月号と重なってしまうが,DBサーバーType1の使用方法についてもう一度説明しておく.
まず,サーバー側でODBCの設定をする必要がある.コントロールパネルからODBCアイコンをダブルクリックしてODBCマネージャを起動する.「システムDSN」タブで「追加」ボタンをクリックして任意のデータベースドライバを選択する.ここでは「Microsoft Access Driver(*.mdb)」を選択することにする.「ODBC Microsoft Access 97セットアップ」が表示されるので,データソース名に任意の名称を指定し,「選択」ボタンをクリックしてアクセスするMDBファイルを設定する.ここでは,Visual Basicがインストールされているフォルダ内にあるBiblio.mdbをBIBLIOというデータソース名で設定することにする(図3).
つぎに,データベースサーバーを使用するには,Easy Socketコントロールをインストールしておく必要がある.

| 最初にサーバーを起動する |
|---|
準備が整ったら,サーバープログラムを起動して,「ODBCデータソース名」項目にBIBLIOと入力して「データベース接続」ボタンをクリックする.これでDBサーバーType1の接続準備は完了である(図4).

次にクライアントアプリケーションを起動する.クライアントマシンとサーバーマシンが同一の場合はそのままでよいが,分かれている場合はフォームのLoadイベントプロシージャの「LocalHostName」の部分をサーバーのマシン名に変更する.
KnSocket1.RemoteHostName = _ KnSocket1.LocalHostName
これでSQL文を図5のように記述して「実行」ボタンをクリックすればサーバーのデータベースにアクセスする.BIBLIO.mdbのテーブルはどれもデータ件数が多いので,抽出条件を指定しないとかなりの時間がかかるので注意が必要である.

| プログラムの動作メカニズム |
|---|
フォームをロードする時,Easy SocketコントロールのOpenメソッドによって,通信の待機状態に入る.
「データベースの接続」ボタンをクリックしたときに実行されるのが,次のイベントプロシージャである.
Private Sub Command1_Click() Set en = rdoEnvironments(0) Set cn = en.OpenConnection(dsName:=Text1, _ Prompt:=rdDriverCompleteRequired) End Sub
データアクセスするためのRDOを初期化している.なお,ここで生成されたオブジェクトは,フォームのアンロード時に破棄される.
ほとんどの処理はクライアントからデータを受信したときに呼び出されるReceiveDataイベントプロシージャ(リスト3)に記述されている. ReceiveDataイベントプロシージャでは,クライアントが送信したデータ,つまり,SQL文を引数vDataとして受け取る.
検索を実行しているのは,次のコードで呼び出されている「実行」ボタンのイベントプロシージャである.
CMD_search_Click
実際の検索は,このイベントプロシージャ内の次の1行によって行なわれる.
Set rs = cn.OpenResultset _ (Text2.Text, rdOpenKeyset, _ rdConcurReadOnly, rdExecDirect)
Recordsetオブジェクト変数rsに検索結果が格納される.
以下の一連のループ処理は,結果をバリアント配列に格納し,最初の2つの配列要素にはレコード数と項目数を格納する.
Do While Not rs.EOF
ReDim Preserve arr _
((recCount + oneTime + 1) * MaxFieldCount)
For i = 1 To oneTime
For fieldCount = 0 To MaxFieldCount - 1
arr(recCount) = rs(fieldCount)
recCount = recCount + 1
Next
rs.MoveNext
If rs.EOF Then
Exit For
End If
Next
Loop
配列を動的に拡大しながら,レコードセットの内容を格納している.
完成したバリアント配列をクライアントに返信しているのが,次の1行である.
KnSocket1.Sockets(nIndex).SendData arrb
SocketsコレクションのSendDataメソッドの引数に配列名を指定することで,接続しているユーザーに結果を返すことができる.
Private Sub KnSocket1_ReceiveData(ByVal nIndex As Integer, ByVal vData As Variant)
Dim oneTime
Dim i As Integer
Dim recCount As Long
Dim arr() As Variant
Dim headerCount As Integer
Dim fieldCount As Integer
Dim MaxFieldCount As Integer
Text2.Text = vData
CMD_search_Click '検索を実行
MaxFieldCount = rs.rdoColumns.Count '項目数を取得
oneTime = 10 '一度に確保する配列の要素数
headerCount = 2 'ヘッダで使用する要素数(レコード数と項目数)
recCount = headerCount
Do While Not rs.EOF '結果セットをバリアント配列に格納するループ
ReDim Preserve arr((recCount + oneTime + 1) * MaxFieldCount)
Debug.Print recCount + oneTime
For i = 1 To oneTime
For fieldCount = 0 To MaxFieldCount - 1
arr(recCount) = rs(fieldCount)
recCount = recCount + 1
Next
rs.MoveNext
If rs.EOF Then
Exit For
End If
Next
Loop
'配列の最初と2番目の要素には,レコード数と項目数を設定
arr(0) = recCount - headerCount
arr(1) = MaxFieldCount
KnSocket1.Sockets(nIndex).SendData arr '結果をバリアント配列でクライアントに返信
rs.Close
Set rs = Nothing
End Sub
|
返信されたデータを表示しているのは,クライアントのReceiveDataイベントプロシージャ(リスト4)である.ここでの処理は特筆すべきものはない.受け取った配列のヘッダ部分を表示した後は,ループ内で配列を展開して表示する.
Private Sub KnSocket1_ReceiveData(ByVal nIndex As Integer, ByVal vData As Variant)
Dim reCount As Long
Dim i As Long
Dim headerCount As Integer
Dim maxFieldCount As Integer
Dim fieldCount As Integer
Dim arraySize As Long
Dim arrayPosition As Long
arraySize = vData(0)
maxFieldCount = vData(1)
recCount = arraySize / maxFieldCount 'レコード数を算出
'レコード数と項目数を表示
Text2.Text = vData(0) & "レコード" & maxFieldCount & "項目" + vbCrLf + vbCrLf
headerCount = 2
i = 0
arrayPosition = headerCount
Do While i < recCount '配列を読み取りながら,テキストボックスに表示するためのループ
fieldCount = 0
Do While fieldCount < maxFieldCount
Text2.Text = Text2.Text & vData(arrayPosition) & " "
fieldCount = fieldCount + 1
arrayPosition = arrayPosition + 1
Loop
Text2.Text = Text2.Text + vbCrLf
i = i + 1
Loop
mlngEndTime = timeGetTime() '計測タイマーを停止
Label3 = (mlngEndTime - mlngStartTime) / 1000
End Sub
|
| 来月は… |
|---|
今月はVisual BasicでWebサーバーを作ったので,来月はHTTPクライアントを作成する.HTTPクライアントはデータベースに関係ないと思われるかもしれないが,実はHTTPクライアントさえあれば簡単にデータベースにアクセスできるのである.しかも,シングルプロセス,マルチスレッドで動作し,ストアドプロシージャも使用可能な本格的なC/Sシステムである.
期待していただきたい.
|
サンプルプログラムのダウンロード |