|
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つのサブルーチンがシステムで提供されています。詳細は、「マルチスレッドおよびマルチコンテキスト・アプリケーションのプログラミング」を参照してください。 |
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)があります。これらの関数を使用してリソース・マネージャをオープンしたりクローズするアプリケーションは、その意味では移植性があります。これらは、構成ファイルに設定されたリソース・マネージャのインスタンス固有の情報にアクセスすることによって動作します。
| 注意: | マルチスレッド・サーバーのコーディングでは、「マルチスレッドおよびマルチコンテキスト・アプリケーションのプログラミング」に説明されているように、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()を呼び出してリソース・マネージャをクローズします。
| 注意: | マルチスレッド・サーバーのコーディングでは、「マルチスレッドおよびマルチコンテキスト・アプリケーションのプログラミング」に説明されているように、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アプリケーションの設定』を参照してください。 |
withdrawalサービス呼出しのシナリオは、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);
実行可能なサーバーをビルドするには、buildserver(1)コマンドを使用して、Oracle Tuxedo Systemサーバー・アダプタなどすべての参照ファイルと共にアプリケーション・サービス・サブルーチンをコンパイルします。
| 注意: | Oracle Tuxedoサーバー・アダプタは、メッセージの受信、処理のディスパッチ、トランザクションが有効な場合はトランザクションの管理を行います。 |
buildserver -ofilename-ffilenames-lfilenames-s -v
表5-6は、buildserverコマンドライン・オプションを示しています。
実行可能サーバーにリンクされるリソース・マネージャのアクセス・ライブラリのリスト。アプリケーション管理者は、buildtms(1)コマンドを使用して、すべての有効なリソース・マネージャ情報を
$TUXDIR/updataobj/RMファイルに事前に定義しておく必要があります。リソース・マネージャは1つしか指定できません。詳細は、『Oracle Tuxedoアプリケーションの設定』を参照してください。
|
|
| 注意: | Oracle Tuxedoライブラリは自動的にリンクされます。 コマンドラインにOracle Tuxedoライブラリ名を指定する必要はありません。 |
リンクするライブラリ・ファイルの指定順序は重要です。関数を呼び出す順序と、それらの関数への参照を含むライブラリによって、この順序が決定されます。
デフォルトでは、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
アプリケーション・サーバーの開発時に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環境変数を設定します。詳細は、「環境変数の設定」を参照してください。
|