クライアント接続クロージャーコールバックでは、サーバーが、クライアントの接続が切断されていることを検知し、クライアント接続が切断されたことによるエラーから回復できるようにします。
接続クロージャーコールバックは、接続上でリクエストが現在全く処理されていない時に呼び出されます。リクエストが処理される時にクライアント接続が切断されると、サーバーはそのリクエストを処理しますが、応答がクライアントに送られないことがあります。接続クロージャーコールバックは、すべての待機中のリクエストが完了した時に呼び出されます。
接続の切断が起こると、トランスポート層がクライアントへエラーメッセージを送信します。たとえば次のように、svc_control() を使用してハンドラがサービスに添付されます:
svc_control(service, SVCSET_RECVERRHANDLER, handler);
サービスまたはこのサービスのインスタンス。引数がサービスの場合は、そのサービスへのすべての新規の接続はエラーハンドラを継承します。引数がサービスのインスタンスの場合は、この接続だけがエラーハンドラを取得します。
エラーハンドラのコールバック。このコールバック関数のプロトタイプは次のようになります:
void handler(const SVCXPRT *svc, const boot_t IsAConnection);
詳細は svc_control(3NSL) のマニュアルページを参照してください。
XDR 非整列化エラーについては、サーバーがリクエストを非整列化できない場合、メッセージは破棄されエラーが直接クライアントへ返されます。
この例では、メッセージログサーバーを実装しています。クライアントは、ログ (実体はテキストファイル) を開いたり、メッセージログを保存したり、ログを閉じたりするのにこのサーバーを使用することができます。
log.x ファイルは、ログプログラムのインタフェースを記述します。
enum log_severity { LOG_EMERG=0, LOG_ALERT=1, LOG_CRIT=2, LOG_ERR=3, LOG_WARNING=4, LOG_NOTICE=5, LOG_INFO=6 }; program LOG { version LOG_VERS1 { int OPENLOG(string ident) = 1; int CLOSELOG(int logID) = 2; oneway WRITELOG(int logID, log_severity severity, string message) = 3; } = 1; } = 0x20001971;
2 つのプロシージャ (OPENLOG および CLOSELOG) は、logID で指定されたログをそれぞれ開いたり閉じたりします。WRITELOG() プロシージャ (この例では oneway として宣言されている) は、開かれたログにメッセージを記録します。ログメッセージは、重要度属性およびテキストメッセージを含みます。
これは、ログサーバーの Makefile です。この Makefile を使用して、log.x ファイルを呼び出します。
RPCGEN = rpcgen CLIENT = logClient CLIENT_SRC = logClient.c log_clnt.c log_xdr.c CLIENT_OBJ = $(CLIENT_SRC:.c=.o) SERVER = logServer SERVER_SRC = logServer.c log_svc.c log_xdr.c SERVER_OBJ = $(SERVER_SRC:.c=.o) RPCGEN_FILES = log_clnt.c log_svc.c log_xdr.c log.h CFLAGS += -I. RPCGEN_FLAGS = -N -C LIBS = -lsocket -lnsl all: log.h ./$(CLIENT) ./$(SERVER) $(CLIENT): log.h $(CLIENT_OBJ) cc -o $(CLIENT) $(LIBS) $(CLIENT_OBJ) $(SERVER): log.h $(SERVER_OBJ) cc -o $(SERVER) $(LIBS) $(SERVER_OBJ) $(RPCGEN_FILES): log.x $(RPCGEN) $(RPCGEN_FLAGS) log.x clean: rm -f $(CLIENT_OBJ) $(SERVER_OBJ) $(RPCGEN_FILES)
logServer.c は、ログサーバーの実装を示します。ログサーバーは、ログメッセージを保存するためにファイルを開くため、openlog_1_svc() にクロージャー接続コールバックを登録します。クライアントプログラムが closelog() プロシージャを呼び出すことを忘れた (または呼び出す前にクラッシュした) 場合でも、ファイル記述子が閉じられるようにこのコールバックが使用されます。この例は、RPC サーバー内のクライアントに関連付けられたリソースを解放するのに接続クロージャーコールバック機能を使用する方法を例示しています。
#include "log.h" #include <stdio.h> #include <string.h> #define NR_LOGS 3 typedef struct { SVCXPRT* handle; FILE* filp; char* ident; } logreg_t; static logreg_t logreg[NR_LOGS]; static char* severityname[] = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Information"}; static void close_handler(const SVCXPRT* handle, const bool_t); static int get_slot(SVCXPRT* handle) { int i; for (i = 0; i < NR_LOGS; ++i) { if (handle == logreg[i].handle) return i; } return -1; } static FILE* _openlog(char* logname) /* * ログファイルを開く */ { FILE* filp = fopen(logname, "a"); time_t t; if (NULL == filp) return NULL; time(&t); fprintf(filp, "Log opened at %s\n", ctime(&t)); return filp; } static void _closelog(FILE* filp) { time_t t; time(&t); fprintf(filp, "Log close at %s\n", ctime(&t)); /* * ログファイルを閉じる */ fclose(filp); } int* openlog_1_svc(char* ident, struct svc_req* req) { int slot = get_slot(NULL); FILE* filp; static int res; time_t t; if (-1 != slot) { FILE* filp = _openlog(ident); if (NULL != filp) { logreg[slot].filp = filp; logreg[slot].handle = req->rq_xprt; logreg[slot].ident = strdup(ident); /* * クライアントが clnt_destroy を呼び出すか、 * クライアントの接続が切断されて clnt_destroy が自動的に呼び出されると、 * サーバーは close_handler コールバックを実行する */ if (!svc_control(req->rq_xprt, SVCSET_RECVERRHANDLER, (void*)close_handler)) { puts("Server: Cannot register a connection closure callback"); exit(1); } } } res = slot; return &res; } int* closelog_1_svc(int logid, struct svc_req* req) { static int res; if ((logid>= NR_LOGS) || (logreg[logid].handle != req->rq_xprt)) { res = -1; return &res; } logreg[logid].handle = NULL; _closelog(logreg[logid].filp); res = 0; return &res; } /* * メッセージをログへ書き込むようリクエストがあると、 * write_log_1_svc が呼び出される */ void* writelog_1_svc(int logid, log_severity severity, char* message, struct svc_req* req) { if ((logid>= NR_LOGS) || (logreg[logid].handle != req->rq_xprt)) { return NULL; } /* * メッセージをファイルへ書き込む */ fprintf(logreg[logid].filp, "%s (%s): %s\n", logreg[logid].ident, severityname[severity], message); return NULL; } static void close_handler(const SVCXPRT* handle, const bool_t dummy) { int i; /* * クライアントの接続が切断されると、closelog でログが閉じられる */ for (i = 0; i < NR_LOGS; ++i) { if (handle == logreg[i].handle) { logreg[i].handle = NULL; _closelog(logreg[i].filp); } } }
logClient.c ファイルは、ログサーバーを使用するクライアントを示しています。
#include "log.h" #include <stdio.h> #define MSG_SIZE 128 void usage() { puts("Usage: logClient <logserver_addr>"); exit(2); } void runClient(CLIENT* clnt) { char msg[MSG_SIZE]; int logID; int* result; /* * クライアントがログを開く */ result = openlog_1("client", clnt); if (NULL == result) { clnt_perror(clnt, "openlog"); return; } logID = *result; if (-1 == logID) { puts("Cannot open the log."); return; } while(1) { struct rpc_err e; /* * クライアントがメッセージをログに書き込む */ puts("Enter a message in the log (\".\" to quit):"); fgets(msg, MSG_SIZE, stdin); /* * 末尾の CR を削除する */ msg[strlen(msg)-1] = 0; if (!strcmp(msg, ".")) break; if (writelog_1(logID, LOG_INFO, msg, clnt) == NULL) { clnt_perror(clnt, "writelog"); return; } } /* * クライアントがログを閉じる */ result = closelog_1(logID, clnt); if (NULL == result) { clnt_perror(clnt, "closelog"); return; } logID = *result; if (-1 == logID) { puts("Cannot close the log."); return; } } int main(int argc, char* argv[]) { char* serv_addr; CLIENT* clnt; if (argc != 2) usage(); serv_addr = argv[1]; clnt = clnt_create(serv_addr, LOG, LOG_VERS1, "tcp"); if (NULL == clnt) { clnt_pcreateerror("Cannot connect to log server"); exit(1); } runClient(clnt); clnt_destroy(clnt); }