Oracle Tuxedoシステムには、ATMIサーバーを簡単に開発できるように、サーバーのロード・モジュール用に定義済のmain()
ルーチンが提供されています。buildserverコマンドを実行すると、main()
ルーチンが自動的にサーバーの一部として組み込まれます。
注: | main() はシステムで提供されたルーチンのため、変更することはできません。 |
定義済main()
ルーチンは、アプリケーションへの参加と終了の他に、サーバーにかわって次の操作を行います。
SIGHUP
シグナルを無視します。 SIGTERM
)を受信すると、終了処理を開始します。サーバーは停止され、必要な場合は再起動します。main()
とリンクされたすべてのサービス、またはOracle Tuxedoシステムの管理者が構成ファイルに指定したサブセットです。--
)までの引数を処理します。2つのダッシュは、システムで認識される引数の終わりを示します。--
)の後に入力された引数を処理したり、リソース・マネージャをオープンします(オプション)。このようなコマンド行の引数は、アプリケーション固有の初期化に使用されます。main()
は次の処理を行います。 main()
は次の処理を行います。 以上からわかるように、main()
ルーチンは、アプリケーションへの参加と終了、バッファやトランザクションの管理、および通信に関する詳細を扱っています。
注: | システムで提供されるmain() は、アプリケーションへの参加と終了を行うため、tpinit()またはtpterm()関数の呼出しをコードに記述しないでください。これらの関数を呼び出すとエラーが発生して、tperrno にTPEPROTO が戻されます。tpinit()またはtpterm()関数の詳細は、「クライアントのコーディング」を参照してください。 |
main()
ルーチンは、システムで提供される1つのATMIサーバーAUTHSVR
、および2つのサブルーチンtpsvrinit()
とtpsvrdone()
を提供します。次の項で説明するこれらの3つのデフォルト・バージョンについては、ご使用のアプリケーションに合せて変更できます。
注: | tpsvrinit() とtpsvrdone() を独自にコーディングする場合、この2つのルーチンのデフォルト・バージョンが、それぞれtx_open() とtx_close() を呼び出すことに注意してください。tx_open() ではなくtpopen() を呼び出すtpsvrinit() の新しいバージョンをコーディングする場合は、tpclose() を呼び出すtpsvrdone() もコーディングする必要があります。つまり、この2つの関数が呼び出すオープンとクローズの関数は対になっている必要があります。 |
注: | ここで説明するサブルーチンの他に、tpsvrthrinit(3c)とtpsvrthrdone(3c)の2つのサブルーチンがシステムで提供されています。詳細は、「マルチスレッドおよびマルチコンテキストATMIアプリケーションのプログラミング」を参照してください。 |
AUTHSVR(5)サーバーを使用すると、アプリケーションで各クライアントの認証を行うことができます。このサーバーは、アプリケーションのセキュリティ・レベルがTPAPPAUTH
に設定されている場合に、tpinit()関数によって呼び出されます。
AUTHSVR
のサービスは、TPINIT
バッファのdata
フィールドでユーザー・パスワードを確認します(TPINIT
バッファのpasswd
フィールドに指定されているアプリケーション・パスワードと混同しないでください)。デフォルトでは、システムによってdata
から文字列が取得され、それと合致する文字列が/etc/passwd
ファイルで検索されます。
tpinit()
がネイティブ・サイトのクライアントから呼び出された場合、受信したdata
フィールドはそのまま転送されます。そのため、アプリケーションでパスワードの暗号化が必要な場合、それに応じてクライアント・プログラムをコーディングする必要があります。
tpinit()
がワークステーション・クライアントから呼び出された場合、データは暗号化されてからネットワークに送信されます。
サーバーの起動時に、Oracle Tuxedoシステムのmain()
はその初期化時、つまりサービス・リクエストの処理を開始する前に、tpsvrinit(3c)を呼び出します。
アプリケーションがこの関数のカスタム・バージョンをサーバーに提供していない場合、main()
で提供されるデフォルトの関数が使用され、この関数は、リソース・マネージャをオープンし、エントリを中央イベント・ログに記録してサーバーの起動が成功したことを示します。中央ユーザー・ログは自動的に生成されるファイルで、userlog(3c)関数を呼び出して、プロセスがこのファイルにメッセージを書き込みます。中央イベント・ログの詳細は、「エラーの管理」を参照してください。
tpsvrinit()
関数を使用すると、アプリケーションで要求される次のような初期化プロセスを行うことができます。
以降の項では、tpsvrinit()
を呼び出すことによって、これらの初期化タスクがどのように行われるのかをサンプル・コードで示します。次のサンプル・コードでは示していませんが、このルーチン内ではメッセージ交換を行うこともできます。ただし、非同期応答が未処理のままでtpsvrinit()
ルーチンが制御を戻すと、このルーチンは失敗します。その場合、Oracle Tuxedoシステムでは応答は無視されて、サーバーが正常に終了します。
また、「エラーの管理」で説明されているように、tpsvrinit()
関数で、トランザクションを開始したり終了することもできます。
tpsvrinit()
関数の呼出しには、次のシグネチャを使用します。
int
tpsvrinit(int argc, char **argv)
サーバーは、起動時に最初の処理として、構成ファイルに指定されているサーバー・オプションをEOFまで読み取ります。その場合、サーバーはーUNIX関数getopt
(3)を呼び出します。コマンド行に2つのダッシュ(--
)が出現した時点で、getopt()
関数はEOFを戻します。getopt
関数は、次に処理する引数のargv
索引を外部変数optind
に設定します。その後、定義済のmain()
関数がtpsvrinit()
を呼び出します。
リスト5-1は、tpsvrinit()
関数を使用してコマンド行オプションを取得する方法を示しています。
tpsvrinit(argc, argv)
int argc;
char **argv;
{
int c;
extern char *optarg;
extern int optind;
.
.
.
while((c = getopt(argc, argv, "f:x:")) != EOF)
switch(c){
.
.
.
}
.
.
.
}
main()
がtpsvrinit()
を呼び出すとき、コマンド行の2つのダッシュ(--
)の後の引数を受け取ります。上の例では、オプションf
とx
それぞれがコロンで示される引数を取ります。optarg
が、オプションの引数の開始位置を示します。switch文のロジックは省略されています。
次のサンプル・コードは、tpsvrinit()
のもう1つの使用法として、リソース・マネージャをオープンする方法を示しています。Oracle Tuxedoシステムで提供される関数によって、リソース・マネージャ、tpopen(3c)およびtx_open(3c)がオープンされます。補助的な関数tpclose(3c)とtx_close(3c)も提供されます。これらの関数を使用してリソース・マネージャをオープンしたりクローズするアプリケーションは、その意味では移植性があります。これらは、構成ファイルに設定されたリソース・マネージャのインスタンス固有の情報にアクセスすることによって動作します。
注: | マルチスレッド・サーバーのコーディングでは、「マルチスレッドおよびマルチコンテキストATMIアプリケーションのプログラミング」に説明されているように、tpsvrthrinit() 関数を使用して、リソース・マネージャを開く必要があります。 |
これらの関数呼出しは省略可能で、リソース・マネージャがデータベースの場合、リソース・マネージャ固有の呼出しがデータ操作言語(DML)の一部であるときは、その呼出しのかわりに使用できます。userlog(3c)関数を使用して、中央イベント・ログに書き込んでいることに注目してください。
注: | コマンド行オプションを受け取り、データベースをオープンする初期化関数を作成するには、リスト5-2に示すサンプル・コードと前述のサンプル・コードを組み合せます。 |
tpsvrinit()
{
/* Open database */
if (tpopen() == -1) {
(void)userlog("tpsvrinit: failed to open database: ");
switch (tperrno) {
case TPESYSTEM:
(void)userlog("System error\n");
break;
case TPEOS:
(void)userlog("Unix error %d\n",Uunixerr);
break;
case TPEPROTO:
(void)userlog("Called in improper context\n");
break;
case TPERMERR:
(void)userlog("RM failure\n");
break;
}
return(-1); /* causes the server to exit */
}
return(0);
}
初期化時にエラーが発生しないように、サーバーを終了してからサービス・リクエストの処理を開始するようにtpsvrinit()
をコーディングできます。
tpsvrinit()
がtpopen()
を呼び出してリソース・マネージャをオープンするのと同じように、tpsvrdone()
関数はtpclose()
を呼び出してリソース・マネージャをクローズします。
注: | マルチスレッド・サーバーのコーディングでは、「マルチスレッドおよびマルチコンテキストATMIアプリケーションのプログラミング」に説明されているように、tpsvrthrdone() コマンドを使用して、リソース・マネージャを開く必要があります。 |
tpsvrdone()
関数の呼出しには、次のシグネチャを使用します。
void
tpsvrdone()
/* Server termination routine */
アプリケーションにtpsvrdone()
のクローズ用ルーチンが定義されていない場合、main()
で提供されるデフォルトのルーチンがOracle Tuxedoシステムによって呼び出されます。このルーチンは、tx_close
()とuserlog
()を呼び出してリソース・マネージャをクローズし、中央イベント・ログに書き込みます。ログには、サーバーが間もなく終了することを伝えるメッセージが送信されます。
ttpsvrdone()
は、サーバーがサービス・リクエストの処理を終了した後、サーバーが終了する前に呼び出されます。その場合、サーバーはまだシステムの一部なので、特定の規則に従っているかぎり、ルーチン内でさらに通信およびトランザクションが行われる場合があります。これらの規則については、「エラーの管理」を参照してください。
リスト5-3は、tpsvrdone()
関数を使用して、リソース・マネージャをクローズして終了する方法を示しています。
void
tpsvrdone()
{
/* Close the database */
if(tpclose() == -1)
(void)userlog("tpsvrdone: failed to close database: ");
switch (tperrno) {
case TPESYSTEM:
(void)userlog("BEA TUXEDO error\n");
break;
case TPEOS:
(void)userlog("Unix error %d\n",Uunixerr);
break;
case TPEPROTO:
(void)userlog("Called in improper context\n");
break;
case TPERMERR:
(void)userlog("RM failure\n");
break;
}
return;
}
return;
}
通信の詳細はOracle Tuxedoシステムのmain()
ルーチンによって処理されるため、通信の実装よりもアプリケーション・サービスのロジックに集中できます。ただし、システムで提供されるmain()
と互換性を保つために、アプリケーション・サービスが特定の規則に従っている必要があります。これらの規則は、サービス・ルーチンをコーディングするためのサービス・テンプレートとも呼ばれます。次に、これらの規則についてまとめます。これらの規則の詳細は、『Oracle Tuxedo ATMI C言語関数リファレンス』の「tpservice(3c)」リファレンス・ページを参照してください。
return
文と同じように機能しますが、処理が終了すると、呼出し側の関数ではなくOracle Tuxedoシステムのmain()
関数に制御が戻ります。svcinfo
を使用して呼び出され、この引数は、サービス情報が定義された構造体(TPSVCINFO
)を指すポインタです。
すべてのサービス・ルーチンは、TPSVCINFO
構造体を指すポインタで構成される1つの引数を受け取る関数として定義する必要があります。TPSVCINFO
構造体は、atmi.h
ヘッダー・ファイルに定義され、次の情報が含まれています。
charname
[32];
longflags
;
char *data
;
longlen
;
intcd
;
intappkey
;
CLIENTIDcltid
;
表5-1は、TPSVCINFO
データ構造体をまとめたものです。
サービスがトランザクション・モードであるかどうか、または呼出し側が応答を要求しているかどうかをサービスに通知します。サービスをトランザクション・モードにする各方法については、「グローバル・トランザクションのコーディング」を参照してください。
TPTRAN フラグは、サービスがトランザクション・モードであることを示します。tpcall()またはtpacall()のflags パラメータにTPNOTRAN を設定してサービスを呼び出した場合、サービスは現在のトランザクションに参加できません。ただし、トランザクション・モードでサービスを実行することはできます。つまり、呼出し側によってTPNOTRAN 通信フラグが設定されていても、TPTRAN をsvcinfo->flags に設定できます。このような状況の例については、「グローバル・トランザクションのコーディング」を参照してください。
tpacall()で
TPNOREPLY 通信フラグが設定されてサービスが呼び出された場合、flags メンバーはTPNOREPLY に設定されます。呼び出されたサービスが、呼出し側プロセスと同じトランザクションに含まれる場合は、呼出し側に応答を戻す必要があります。
|
|
main() 内でtpalloc()によってすでに割り当てられているバッファを指すポインタ。このバッファは、リクエスト・メッセージの受信に使用されます。ただし、応答メッセージの返信やリクエスト・メッセージの転送にも、このバッファを使用することをお薦めします。
|
|
アプリケーションで使用するために予約された値。アプリケーション固有の認証が設計に含まれている場合、クライアントがアプリケーションに参加するときに呼び出されるアプリケーション固有の認証サーバーによって、認証の成功または失敗を示す値、およびクライアント認証キーが戻される必要があります。Oracle Tuxedoシステムは、
appkey をクライアントのために保持し、このフィールドに情報を格納して以降のサービス・リクエストに渡します。appkey がサービスに渡されたときは、クライアントの認証は終了しています。ただし、サービス内でappkey フィールドを使用して、サービスを呼び出したユーザー、またはそのユーザーに関するその他のパラメータを識別できます。
|
|
プロセスがTPSVCINFO
構造体のdata
フィールドにアクセスする場合、次のバッファ・タイプが合致する必要があります。
リスト5-4は、一般的なサービス定義を示しています。このコードは、Oracle Tuxedoソフトウェアで提供される銀行業務アプリケーションのABAL
(残高照会)サービス・ルーチンから引用したものです。ABAL
はBAL
サーバーの一部です。
#include <stdio.h> /* UNIX */
#include <atmi.h> /* BEA Tuxedo System */
#include <sqlcode.h> /* BEA Tuxedo System */
#include "bank.flds.h" /* bankdb fields */
#include "aud.h" /* BANKING view defines */
EXEC SQL begin declare section;
static long branch_id; /* branch id */
static float bal; /* balance */
EXEC SQL end declare section;
/*
* Service to find sum of the account balances at a SITE
*/
void
#ifdef __STDC__
ABAL(TPSVCINFO *transb)
#else
ABAL(transb)
TPSVCINFO *transb;
#endif
{
struct aud *transv; /* view of decoded message */
/* Set pointer to TPSVCINFO data buffer */
transv = (struct aud *)transb->data;
set the consistency level of the transaction
/* Get branch id from message, do query */
EXEC SQL declare acur cursor for
select SUM(BALANCE) from ACCOUNT;
EXEC SQL open acur; /* open */
EXEC SQL fetch acur into :bal; /* fetch */
if (SQLCODE != SQL_OK) { /* nothing found */
(void)strcpy (transv->ermsg,"abal failed in sql aggregation");
EXEC SQL close acur;
tpreturn(TPFAIL, 0, transb->data, sizeof(struct aud), 0);
}
EXEC SQL close acur;
transv->balance = bal;
tpreturn (TPSUCCESS, 0, transb->data, sizeof(struct aud), 0);
}
前述の例では、アプリケーションがクライアント側にリクエスト・バッファを割り当てるために、tpalloc()の呼出しでtype
パラメータにVIEW
、subtype
にaud
が設定されています。ABAL
サービスは、VIEW
型付きバッファをサポートするサービスとして定義されています。ABAL
にはBUFTYPE
パラメータは指定されず、デフォルト値のALL
が使用されます。ABAL
サービスはVIEW
型のバッファを割り当て、このバッファへのポインタにABAL
サブルーチンが渡されたTPSVCINFO
構造体のdata
メンバーを割り当てます。ABAL
サーバーは、前述のサンプル・コードに示してあるように、対応するdata
メンバーにアクセスして適切なデータ・バッファを取得します。
注: | このバッファが取得された後、データベースへの最初のアクセスを行う前に、サービスでトランザクションの整合性レベルを指定する必要があります。トランザクションの整合性レベルの詳細は、「グローバル・トランザクションのコーディング」を参照してください。 |
この項のコード例は、サービスがTPSVCINFO
構造体で定義されたデータ・バッファにアクセスして、tptypes()関数を使用して型を判別する方法を示します。(このプロセスは、「バッファ・タイプの確認」で説明しています。)このサービスでは、バッファの領域を再割り当てするかどうかを確認するために、バッファの最大サイズもチェックされます。
このサンプル・コードは、Oracle Tuxedoソフトウェアで提供される銀行業務アプリケーションのABAL
サービスから引用したものです。これは、aud VIEW
またはFML
のいずれかのバッファとしてリクエストを受け入れるサービスをコーディングする方法を示しています。メッセージ・タイプの確認が失敗した場合、エラー・メッセージの文字列と該当の戻りコードが戻され、成功した場合は、バッファ・タイプに合致したコードのセグメントが実行されます。tpreturn()関数の詳細は、「サービス・ルーチンの終了」を参照してください。
#define TMTYPERR 1 /* return code indicating tptypes failed */
#define INVALMTY 2 /* return code indicating invalid message type */
void
ABAL(transb)
TPSVCINFO *transb;
{
struct aud *transv; /* view message */
FBFR *transf; /* fielded buffer message */
int repc; /* tpgetrply return code */
char typ[TMTYPELEN+1], subtyp[TMSTYPELEN+1]; /* type, subtype of message */
char *retstr; /* return string if tptypes fails */
/* find out what type of buffer sent */
if (tptypes((char *)transb->data, typ, subtyp) == -1) {
retstr=tpalloc("STRING", NULL, 100);
(void)sprintf(retstr,
"Message garbled; tptypes cannot tell what type message\n");
tpreturn(TPFAIL, TMTYPERR, retstr, 100, 0);
}
/* Determine method of processing service request based on type */
if (strcmp(typ, "FML") == 0) {
transf = (FBFR *)transb->data;
... code to do abal service for fielded buffer ...
tpreturn succeeds and sends FML buffer in reply
}
else if (strcmp(typ, "VIEW") == 0 && strcmp(subtyp, "aud") == 0) {
transv = (struct aud *)transb->data;
... code to do abal service for aud struct ...
tpreturn succeeds and sends aud view buffer in reply
}
else {
retstr=tpalloc("STRING", NULL, 100);
(void)sprintf(retstr,
"Message garbled; is neither FML buffer nor aud view\n");
tpreturn(TPFAIL, INVALMTY, retstr, 100, 0);
}
}
注: | 優先度を取得するtpgprio() 、および優先度を設定するtpsprio() 関数の詳細は、「メッセージの優先度の設定および取得」を参照してください。 |
この項で示すサンプル・コードは、PRINTER
という名前のサービスがtpgprio()関数を使用して、受信したばかりのリクエストの優先度を確認する方法を示しています。その後、その優先度に基づいて印刷ジョブが適切なプリンタに送られ、そのプリンタにpbuf
data
の内容がパイプされています。
アプリケーションはpbuf
flags
に対して問合せを実行し、応答が必要かどうかを判定します。その場合は、宛先プリンタ名をクライアントに返します。tpreturn()関数の詳細は、「サービス・ルーチンの終了」を参照してください。
#include <stdio.h>
#include "atmi.h"
char *roundrobin();
PRINTER(pbuf)
TPSVCINFO *pbuf; /* print buffer */
{
char prname[20], ocmd[30]; /* printer name, output command */
long rlen; /* return buffer length */
int prio; /* priority of request */
FILE *lp_pipe; /* pipe file pointer */
prio=tpgprio();
if (prio <= 20)
(void)strcpy(prname,"bigjobs"); /* send low priority (verbose)
jobs to big comp. center
laser printer where operator
sorts output and puts it
in a bin */
else if (prio <= 60)
(void)strcpy(prname,roundrobin()); /* assign printer on a
rotating basis to one of
many local small laser printers
where output can be picked
up immediately; roundrobin() cycles
through list of printers */
else
(void)strcpy(prname,"hispeed");
/* assign job to high-speed laser
printer; reserved for those who
need verbose output on a daily,
frequent basis */
(void)sprintf(ocmd, "lp -d%s", prname); /* output lp(1) command */
lp_pipe = popen(ocmd, "w"); /* create pipe to command */
(void)fprintf(lp_pipe, "%s", pbuf->data); /* print output there */
(void)pclose(lp_pipe); /* close pipe */
if ((pbuf->flags & TPNOREPLY))
tpreturn(TPSUCCESS, 0, NULL, 0, 0);
rlen = strlen(prname) + 1;
pbuf->data = tprealloc(pbuf->data, rlen); /* ensure enough space for name */
(void)strcpy(pbuf->data, prname);
tpreturn(TPSUCCESS, 0, pbuf->data, rlen, 0);
char *
roundrobin()
{
static char *printers[] = {"printer1", "printer2", "printer3", "printer4"};
static int p = 0;
if (p > 3)
p=0;
return(printers[p++]);
}
tpreturn(3c)、tpcancel(3c)、およびtpforward(3c)関数は、サービス・ルーチンが完了したことをそれぞれ次の方法で通知します。
tpreturn(3c)関数はサービス・ルーチンの終了を示し、リクエスタにメッセージを送ります。tpreturn()
関数の呼出しには、次のシグネチャを使用します。
void
tpreturn(int rval, int rcode, char *data, long len, long flags)
表5-2は、tpreturn()
関数の引数を示しています。
この引数の値がグローバル・トランザクションに与える影響については、「グローバル・トランザクションのコーディング」を参照してください。
|
|||
アプリケーション定義の戻りコードを呼出し側に返します。クライアントは、グローバル変数tpurcode(5)に対して問合せを実行して、
rcode に返された値にアクセスできます。成功または失敗に関係なく、このコードは関数から返されます。
|
|||
クライアント・プロセスに返される応答メッセージを指すポインタ。メッセージ・バッファは、tpalloc()を使用して事前に割り当てられていることが必要です。
SVCINFO 構造体のサービスに渡されたバッファと同じものを使用する場合は、バッファの割り当ておよび解放について意識する必要はありません。この2つの処理は、システムで提供されるmain() 関数で行われます。このバッファは、tpfree()コマンドを使用しても解放できません。このコマンドを使ってバッファの解放を試みると失敗します。tprealloc()関数を使用すると、バッファのサイズを変更できます。
サービス・ルーチンに渡されたバッファとは別のバッファを使ってメッセージを返す場合、自分でそのバッファを割り当てる必要があります。アプリケーション側でtpreturn()関数が呼び出されると、バッファは自動的に解放されます。
|
|||
サービス・ルーチンの主なタスクは、リクエストを処理してクライアント・プロセスに応答を返すことです。ただし、要求されたタスクを行うために必要なすべての処理を1つのサービスで行う必要はありません。サービスはリクエスタとして動作し、クライアントが元のリクエストを行ったときと同じように、tpcall()またはtpacall()を呼び出してリクエストを別のサービスに渡すことができます。
注: | tpcall()とtpacall()関数の詳細は、「クライアントおよびサーバーへのリクエスト/レスポンスのコーディング」を参照してください。 |
tpreturn()が呼び出された場合、常にmain()
関数に制御が戻ります。非同期応答でサービスがリクエストを送信している場合、main()
に制御を戻す前にすべての応答を受信するか、またはtpcancel()を使用してすでに送信したリクエストを無効にする必要があります。それ以外の場合、未処理の応答はOracle Tuxedoシステムのmain()
で受信されると自動的に破棄され、呼出し側にエラーが返されます。
クライアントがtpcall()を使用してサービスを呼び出した場合、tpreturn()の呼出しが成功すると、応答メッセージが*odata
ポインタで示されるバッファ内に格納されます。tpacall()を使用してリクエストを送信し、tpreturn()
から正常に制御が戻されると、応答メッセージは*data
ポインタが指すtpgetrply()のバッファ内に格納されます。
応答が必要な場合に、tpreturn()の引数の処理時にエラーが発生すると、呼出し側プロセスに失敗を示すメッセージが送信されます。呼出し側は、
tperrno
に格納されている値を調べてエラーを検出します。失敗を示すメッセージが送信された場合、tperrno
にTPESVCERR
が設定されます。この値は、グローバル変数tpurcode
の値よりも優先されます。このようなエラーが発生した場合、応答データは返されず、呼出し側の出力バッファの内容と長さは変更されません。
tpreturn()が不明なタイプのバッファにメッセージを返すか、または呼出し側で使用できないバッファにメッセージを返した場合、つまりflags
にTPNOCHANGE
が設定されて呼出しが行われた場合、tperrno(5)にTPEOTYPE
が返されます。その場合、アプリケーションの成功また失敗は判定されず、呼出し側の出力バッファの内容と長さは変更されません。
tpreturn()関数が呼び出され、呼出し側が応答を待っている間にタイムアウトが発生した場合、グローバル変数tpurcode(5)に戻される値は意味を持ちません。この状況は、tperrno(5)に値が戻されるどの状況よりも優先します。その場合は、tperrno(5)にTPETIME
が設定され、応答データは送信されず、呼出し側の応答バッファの内容と長さは変更されません。Oracle Tuxedoシステムには、ブロッキング・タイムアウトとトランザクション・タイムアウトの2種類のタイムアウトがあります(詳細は、「グローバル・トランザクションのコーディング」を参照)。
ここで示すサンプル・コードは、XFER
サーバーの一部であるTRANSFER
サービスを示しています。基本的に、TRANSFER
サービスはWITHDRAWAL
およびDEPOSIT
サービスへの同期呼出しを行います。このサービスでは、WITHDRAWAL
とDEPOSIT
の両サービスの呼出しに同じリクエスト・バッファを使用する必要があるので、応答メッセージ用に別のバッファが割り当てられます。WITHDRAWAL
の呼出しが失敗した場合、フォーム上のステータス行に「cannot withdraw」
というメッセージが出力され、応答バッファが解放され、tpreturn()関数のrval
引数にTPFAIL
が設定されます。呼出しが成功した場合、振替元口座の残高が応答バッファから取得されます。
注: | 次のサンプル・コードでは、フィールド化バッファtransf 内のACCOUNT_ID フィールドのゼロ番目のオカレンスに、アプリケーションがcr_id 変数から取得した振替先口座の識別子を移動しています。このような移動が必要なのは、FML バッファ内のフィールドのこのオカレンスが、データ依存型ルーティングに使用されるからです。詳細は、『Oracle Tuxedoアプリケーションの設定』を参照してください。 |
DEPOSIT
サービスへの呼出しは、同様のシナリオに従います。呼出しが成功すると、このサービスによってサービス・ルーチンに割り当てられていた応答バッファが解放され、rval
引数にTPSUCCESS
が設定されて、適切な残高情報がステータス行に返されます。
#include <stdio.h> /* UNIX */
#include <string.h> /* UNIX */
#include "fml.h" /* BEA Tuxedo System */
#include "atmi.h" /* BEA Tuxedo System */
#include "Usysflds.h" /* BEA Tuxedo System */
#include "userlog.h" /* BEA Tuxedo System */
#include "bank.h" /* BANKING #defines */
#include "bank.flds.h" /* bankdb fields */
/*
* Service to transfer an amount from a debit account to a credit
* account
*/
void
#ifdef __STDC__
TRANSFER(TPSVCINFO *transb)
#else
TRANSFER(transb)
TPSVCINFO *transb;
#endif
{
FBFR *transf; /* fielded buffer of decoded message */
long db_id, cr_id; /* from/to account id’s */
float db_bal, cr_bal; /* from/to account balances */
float tamt; /* amount of the transfer */
FBFR *reqfb; /* fielded buffer for request message*/
int reqlen; /* length of fielded buffer */
char t_amts[BALSTR]; /* string for transfer amount */
char db_amts[BALSTR]; /* string for debit account balance */
char cr_amts[BALSTR]; /* string for credit account balance */
/* Set pointr to TPSVCINFO data buffer */
transf = (FBFR *)transb->data;
/* Get debit (db_id) and credit (cr_id) account IDs */
/* must have valid debit account number */
if (((db_id = Fvall(transf, ACCOUNT_ID, 0)) < MINACCT) || (db_id > MAXACCT)) {
(void)Fchg(transf, STATLIN, 0,"Invalid debit account number",(FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* must have valid credit account number */
if ((cr_id = Fvall(transf, ACCOUNT_ID, 1)) < MINACCT || cr_id > MAXACCT) {
(void)Fchg(transf,STATLIN, 0,"Invalid credit account number",(FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* get amount to be withdrawn */
if (Fget(transf, SAMOUNT, 0, t_amts, < 0) 0 || strcmp(t_amts,"") == 0) {
(void)Fchg(transf, STATLIN, 0, "Invalid amount",(FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
(void)sscanf(t_amts,"%f",tamt);
/* must have valid amount to transfer */
if (tamt = 0.0) {
(void)Fchg(transf, STATLIN, 0,
"Transfer amount must be greater than $0.00",(FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* make withdraw request buffer */
if ((reqfb = (FBFR *)tpalloc("FML",NULL,transb->len)) == (FBFR *)NULL) {
(void)userlog("tpalloc failed in transfer\n");
(void)Fchg(transf, STATLIN, 0,
"unable to allocate request buffer", (FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
reqlen = Fsizeof(reqfb);
/* put ID in request buffer */
(void)Fchg(reqfb,ACCOUNT_ID,0,(char *)&db_id, (FLDLEN)0);
/* put amount in request buffer */
(void)Fchg(reqfb,SAMOUNT,0,t_amts, (FLDLEN)0);
/* increase the priority of withdraw call */
if (tpsprio(PRIORITY, 0L) == -1)
(void)userlog("Unable to increase priority of withdraw\n");
if (tpcall("WITHDRAWAL", (char *)reqfb,0, (char **)&reqfb,
(long *)&reqlen,TPSIGRSTRT) == -1) {
(void)Fchg(transf, STATLIN, 0,
"Cannot withdraw from debit account", (FLDLEN)0);
tpfree((char *)reqfb);
tpreturn(TPFAIL, 0,transb->data, 0L, 0);
}
/* get "debit" balance from return buffer */
(void)strcpy(db_amts, Fvals((FBFR *)reqfb,SBALANCE,0));
void)sscanf(db_amts,"%f",db_bal);
if ((db_amts == NULL) || (db_bal < 0.0)) {
(void)Fchg(transf, STATLIN, 0,
"illegal debit account balance", (FLDLEN)0);
tpfree((char *)reqfb);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* put deposit account ID in request buffer */
(void)Fchg(reqfb,ACCOUNT_ID,0,(char *)&cr_id, (FLDLEN)0);
/* put transfer amount in request buffer */
(void)Fchg(reqfb,SAMOUNT,0,t_amts, (FLDLEN)0);
/* Up the priority of deposit call */
if (tpsprio(PRIORITY, 0L) == -1)
(void)userlog("Unable to increase priority of deposit\n");
/* Do a tpcall to deposit to second account */
if (tpcall("DEPOSIT", (char *)reqfb, 0, (char **)&reqfb,
(long *)&reqlen, TPSIGRSTRT) == -1) {
(void)Fchg(transf, STATLIN, 0,
"Cannot deposit into credit account", (FLDLEN)0);
tpfree((char *)reqfb);
tpreturn(TPFAIL, 0,transb->data, 0L, 0);
}
/* get "credit" balance from return buffer */
(void)strcpy(cr_amts, Fvals((FBFR *)reqfb,SBALANCE,0));
(void)sscanf(cr_amts,"%f",&cr_bal);
if ((cr_amts == NULL) || (cr_bal 0.0)) {
(void)Fchg(transf, STATLIN, 0,
"Illegal credit account balance", (FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* set buffer for successful return */
(void)Fchg(transf, FORMNAM, 0, "CTRANSFER", (FLDLEN)0);
(void)Fchg(transf, SAMOUNT, 0, Fvals(reqfb,SAMOUNT,0), (FLDLEN)0);
(void)Fchg(transf, STATLIN, 0, "", (FLDLEN)0);
(void)Fchg(transf, SBALANCE, 0, db_amts, (FLDLEN)0);
(void)Fchg(transf, SBALANCE, 1, cr_amts, (FLDLEN)0);
tpfree((char *)reqfb);
tpreturn(TPSUCCESS, 0,transb->data, 0L, 0);
}
tpgetrply()を呼び出したサービス(「クライアントおよびサーバーへのリクエスト/レスポンスのコーディング」を参照)がTPETIME
で失敗してリクエストを取り消す場合、tpcancel(3c)を呼び出して記述子を無効にできます。以降、応答が届いても自動的に破棄されます。
tpcancel()
関数の呼出しには、次のシグネチャを使用します。
void
tpcancel(int cd)
cd
(呼出し記述子)引数には、取り消すプロセスを指定します。
tpcancel()
はトランザクション応答、つまりTPNOTRAN
フラグが設定されていない状態で呼び出されたリクエストへの応答には使用できません。トランザクション内では、tpabort(3c)がトランザクションの呼出し記述子を無効にします。
次のサンプル・コードは、タイムアウト後の応答を無効にする方法を示しています。
int cd1;
.
.
.
if ((cd1=tpacall(sname, (char *)audv, sizeof(struct aud),
TPNOTRAN)) == -1) {
.
.
.
}
if (tpgetrply(cd1, (char **)&audv,&audrl, 0) == -1) {
if (tperrno == TPETIME) {
tpcancel(cd1);
.
.
.
}
}
tpreturn(TPSUCCESS, 0,NULL, 0L, 0);
tpforward(3c)関数を使用すると、サービス・リクエストをほかのサービスに転送して、別の処理を行うことができます。
tpforward
()関数の呼出しには、次のシグネチャを使用します。
void
tpforward(char *svc, char *data, long len, long flags)
表5-3は、tpreturn()
関数の引数を示しています。
クライアント・プロセスに返される応答メッセージを指すポインタ。メッセージ・バッファは、tpalloc()を使用して事前に割り当てられていることが必要です。
SVCINFO 構造体のサービスに渡されたバッファと同じものを使用する場合は、バッファの割り当ておよび解放について意識する必要はありません。この2つの処理は、システムで提供されるmain() 関数で行われます。このバッファは、tpfree()コマンドを使用しても解放できません。このコマンドを使ってバッファの解放を試みると失敗します。tprealloc()関数を使用すると、バッファのサイズを変更できます。
サービス・ルーチンに渡されたバッファとは別のバッファを使ってメッセージを返す場合、自分でそのバッファを割り当てる必要があります。アプリケーション側でtpreturn()関数が呼び出されると、バッファは自動的に解放されます。
|
|||
tpforward
()は、サービス呼出しとは異なります。つまり、リクエストの転送元サービスでは、応答はリクエストされていません。応答を返すのは、リクエストの転送先サービスです。 このサービスを転送されたサービスが、リクエストの発信元プロセスに応答を返します。後者のサービスが、リクエストの発信元プロセスに応答を送信します。転送が連鎖的に行われる場合、最後のサーバーがtpreturn()を呼び出して、要求の発信元であるクライアントに応答を返します。
図5-1は、あるサービスから別のサービスにリクエストを転送したときのイベントの流れを示しています。ここでは、クライアントはtpcall()関数を使用してリクエストを開始し、連鎖の最後のサービス(サービスSVC_C
)がtpreturn()関数を使用して応答を返しています。
サービス・ルーチンはtpsprio()関数を使用して、クライアント・プロセスがリクエストを送るのと同じように、指定された優先度に従ってリクエストを転送できます。
プロセスがtpforward()を呼び出すと、システムで提供されたmain()
に制御が戻り、サーバー・プロセスは別のリクエストを処理できるようになります。
注: | クライアントとして動作するサーバー・プロセスが応答をリクエストする場合、このサーバーが自分自身からサービスをリクエストすることはできません。つまり、必要なサービスの唯一のインスタンスがリクエストを行っているサーバー・プロセスからのみ提供される場合、その呼出しは失敗して再帰呼出しができないことが示されます。ただし、TPNOREPLY 通信フラグが設定された状態でサービス・ルーチンが自分宛てにリクエストを送信または転送した場合、サービスは自分からの応答を待機しないので、呼出しは失敗しません。 |
tpforward()呼出しを使用して、その呼出しを行った時点まで要求の処理が成功していたことを示すことができます。アプリケーション・エラーが検出されなかった場合、tpforward()
を呼び出します。エラーが検出された場合、rval
にTPFAIL
を設定してtpreturn()を呼び出します。
リスト5-9は、ACCT
サーバーの一部であるOPEN_ACCT
サービス・ルーチンから引用したものです。この例は、tpforward()を呼び出して、サービスがそのデータ・バッファをDEPOSIT
サービスに送る方法を示しています。このコードは、SQLCODE
をテストして、口座の挿入が成功したかどうかを調べています。新規口座の追加が成功した場合、支店レコードが更新されてその口座が反映され、データ・バッファがDEPOSIT
サービスに転送されます。失敗した場合、rval
にTPFAIL
が設定されてtpreturn()が呼び出され、失敗を示すメッセージがフォーム上のステータス行に出力されます。
...
/* set pointer to TPSVCINFO data buffer */
transf = (FBFR *)transb->data;
...
/* Insert new account record into ACCOUNT*/
account_id = ++last_acct; /* get new account number */
tlr_bal = 0.0; /* temporary balance of 0 */
EXEC SQL insert into ACCOUNT (ACCOUNT_ID, BRANCH_ID, BALANCE,
ACCT_TYPE, LAST_NAME, FIRST_NAME, MID_INIT, ADDRESS, PHONE) values
(:account_id, :branch_id, :tlr_bal, :acct_type, :last_name,
:first_name, :mid_init, :address, :phone);
if (SQLCODE != SQL_OK) { /* Failure to insert */
(void)Fchg(transf, STATLIN, 0,
"Cannot update ACCOUNT", (FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* Update branch record with new LAST_ACCT */
EXEC SQL update BRANCH set LAST_ACCT = :last_acct where BRANCH_ID = :branch_id;
if (SQLCODE != SQL_OK) { /* Failure to update */
(void)Fchg(transf, STATLIN, 0,
"Cannot update BRANCH", (FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* up the priority of the deposit call */
if (tpsprio(PRIORITY, 0L) == -1)
(void)userlog("Unable to increase priority of deposit\n");
/* tpforward same buffer to deposit service to add initial balance */
tpforward("DEPOSIT", transb->data, 0L, 0);
サーバーは起動時に、構成ファイルのCLOPT
パラメータに指定された値に基づいて、提供するサービスを通知します。
注: | サーバーが通知するサービスは、buildserverコマンドの実行時に最初に定義されます。-s オプションを使用すると、複数のサービスをカンマ区切りで指定できます。また、通知されたサービスと異なる名前の関数を呼び出して、サービス・リクエストを処理できます。詳細は、『Oracle Tuxedoコマンド・リファレンス』の「buildserver(1)」を参照してください。 |
デフォルトでは、サーバーに組み込まれたすべてのサービスをそのサーバーが通知します。詳細は、『ファイル形式、データ記述、MIBおよびシステム・プロセス・リファレンス』の「UBBCONFIG(5)」または「servopts(5)」リファレンス・ページを参照してください。
通知されたサービスでは掲示板のサービス表エントリが使用されるので、リソースが消費される場合があります。そのため、サーバーの起動時には、提供されるサービスのサブセットだけを利用できるようにします。アプリケーションで利用できるサービスを制限するには、構成ファイルのSERVERS
セクションで該当するエントリにCLOPT
パラメータを定義し、-s
オプションの後に必要なサービスをカンマで区切って指定します。また、-s
オプションを使用すると、サービス要求を処理するために呼び出される通知済のサービスと異なる名前の関数を呼び出すこともできます。詳細は、『ファイル形式、データ記述、MIBおよびシステム・プロセス・リファレンス』の「servopts(5)」リファレンス・ページを参照してください。
Oracle Tuxedoアプリケーションの管理者は、tmadmin(1)のadvertise
およびunadvertise
コマンドを使用して、サーバーで提供されるサービスを管理できます。tpadvertise()
およびtpunadvertise()
関数を使用すると、リクエスト/レスポンス型サーバーまたは会話型サーバーでのサービスの通知を動的に制御できます。ただし、通知されるサービス、または通知を解除するサービスは、リクエストを行うサービスと同じサーバー内になければなりません。
tpadvertise(3c)関数の呼出しには、次のシグネチャを使用します。
int
tpadvertise(char *svcname, void *func)
表5-4は、tpadvertise()
関数の引数を示しています。
tpunadvertise(3c)関数は、掲示板のサービス表からサービス名を削除します。サービス名が削除されたサービスは、通知されていない状態になります。
tpunadvertise()
関数の呼出しには、次のシグネチャを使用します。
tpunadvertise(char *svcname)
char *svcname;
tpunadvertise()
関数の引数は、表5-5で説明する引数だけです。
リスト5-10は、tpadvertise()
関数の使用方法を示しています。このコードでは、サーバーTLR
が起動時にTLR_INIT
サービスだけを提供するようにコーディングされています。初期化後、TLR_INIT
はDEPOSIT
とWITHDRAW
という2つのサービスを通知します。両方ともtlr_funcs
関数によって実行され、サーバーTLR
に組み込まれています。
DEPOSIT
とWITHDRAW
を通知した後、TLR_INIT
は自分自身で通知を解除します。
extern void tlr_funcs()
.
.
.
if (tpadvertise("DEPOSIT", (tlr_funcs)(TPSVCINFO *)) == -1)
check for errors;
if (tpadvertise("WITHDRAW", (tlr_funcs)(TPSVCINFO *)) == -1)
check for errors;
if (tpunadvertise("TLR_INIT") == -1)
check for errors;
tpreturn(TPSUCCESS, 0, transb->data,0L, 0);
実行可能なATMIサーバーをビルドするには、buildserver(1)コマンドを使用して、Oracle Tuxedoシステム・サーバー・アダプタなどすべての参照ファイルと共にアプリケーション・サービス・サブルーチンをコンパイルします。
注: | Oracle Tuxedoサーバー・アダプタは、メッセージの受信、処理のディスパッチ、トランザクションが有効な場合はトランザクションの管理を行います。 |
buildserver -ofilename
-ffilenames
-lfilenames
-s -v
表5-6は、buildserver
コマンド行オプションを示しています。
実行可能サーバーにリンクされるリソース・マネージャのアクセス・ライブラリのリスト。アプリケーション管理者は、buildtms(1)コマンドを使用して、すべての有効なリソース・マネージャ情報を
$TUXDIR/updataobj/RM ファイルに事前に定義しておく必要があります。リソース・マネージャは1つしか指定できません。詳細は、『Oracle Tuxedoアプリケーションの設定』を参照してください。
|
|
注: | Oracle Tuxedoライブラリは自動的にリンクされます。コマンド行にOracle Tuxedoライブラリ名を指定する必要はありません。 |
注: | リンクの編集はbuildserver コマンドを実行して行う必要があります。 |
リンクするライブラリ・ファイルの指定順序は重要です。関数を呼び出す順序と、それらの関数への参照を含むライブラリによって、この順序が決定されます。
デフォルトでは、buildserver
コマンドはUNIXのcc
コマンドを呼び出します。環境変数CC
を指定して別のコンパイル・コマンドを指定したり、CFLAGS
を指定してコンパイル・フェーズやリンク・フェーズに独自のフラグを指定することができます。詳細は、「環境変数の設定」を参照してください。
次のコマンドは、acct.o
アプリケーション・ファイルを処理して、NEW_ACCTとCLOSE_ACCTという2つのサービスを含むACCT
サーバーを作成しています。NEW_ACCT
はOPEN_ACCT
関数を呼び出し、CLOSE_ACCT
は同じ名前の関数を呼び出します。
buildserver – o ACCT – f acct.o – s NEW_ACCT:OPEN_ACCT – s CLOSE_ACCT
アプリケーションATMIサーバーの開発時にC++コンパイラを使用する場合と、Cコンパイラを使用する場合では、基本的に次の2点が異なります。
C++コンパイラでサービス関数を宣言する場合、extern “C”
を使用して、“C”リンクを持つサービス関数を宣言しなければなりません。次のように、関数のプロトタイプを指定します。
#ifdef __cplusplus
extern "C"
#endif
MYSERVICE(TPSVCINFO *tpsvcinfo)
"C"リンクを持つサービス名を宣言すると、C++コンパイラによって名前が変更されなくなります。多くのC++コンパイラでは、パラメータおよび関数の戻り値の型情報を含むように関数名が変更されます。
C++のコンストラクタは、クラス・オブジェクトを作成するときに呼び出されて、クラス・オブジェクトを初期化します。デストラクタは、クラス・オブジェクトを破棄するときに呼び出されます。コンストラクタとデストラクタを持つ自動(ローカルで非静的な)変数では、変数がスコープに入るときにコンストラクタが呼び出され、変数がスコープから出るときにデストラクタが呼び出されます。ただし、tpreturn()またはtpforward()関数が呼び出されると、longjmp
(3)を使用して、非ローカルのgotoがコンパイラによって実行され、自動変数のデストラクタは呼び出されません。この問題を防ぐには、tpreturn()
やtpforward()
の呼出しが、サービス・ルーチンから呼び出される関数からではなく、サービス・ルーチンから直接行われるようにアプリケーションをコーディングします。また、次のいずれかの条件を満たす必要があります。
つまり、tpreturn()またはtpforward()関数を呼び出すときに、現在の関数のスコープ内またはスタック上にデストラクタを持つ自動変数が存在しないように、アプリケーションを定義します。
コンストラクタとデストラクタを持つグローバル変数および静的変数を正しく処理するために、多くのC++コンパイラではmain()
のコンパイルにC++コンパイラを使用する必要があります。
注: | main() ルーチンには、プログラム開始時にコンストラクタ、プログラム終了時にデストラクタを確実に実行するための特別な処理が定義されています。 |
main()
はOracle Tuxedoシステムで提供される関数なので、直接コンパイルされることはありません。ファイルがC++を使用してコンパイルされるには、buildserverコマンドでC++コンパイラを使用する必要があります。デフォルトでは、buildserver
コマンドはUNIXのcc
コマンドを呼び出します。buildserver
コマンドからC++コンパイラが呼び出されるようにするには、CC
環境変数にC++コンパイラのフルパス名を設定します。また、C++のコマンド行で指定するオプションにフラグを設定するには、CFLAGS
環境変数を設定します。詳細は、「環境変数の設定」を参照してください。