2007-6-22 16:53
dczyqsj
中创金融系统开发平台(BSDP)(报文通讯部分)
(报文通讯部分)
Ver 1.0
BSDP通讯支持系统(CSS)
目 录
1 总体设计 4
2 报文结构 5
2.1 请求报文格式 5
2.2 应答报文格式 5
2.3 处理结果报文格式 6
3 电子日志格式定义 6
3.1 主机端日志 6
3.2 PC端日志 6
3.2.1 请求报文登记日志 6
3.2.2 结果日志 7
3.3 主机端最新返回报文登记 7
4 配置文件格式 7
4.1 服务进程定义 8
4.2 报文配置 8
4.3 当前流水号 8
4.4 监控屏幕定义 8
4.5 中心机定义(仅用于客户端) 9
5 中心机函数说明 9
STrans.h(S) 9
ServCtrl(S) 11
SendAns(S) 12
SendRes(S) 12
analtext(CS) 12
danaltext(CS) 13
GetPktNote(CS) 13
OutputOnCon(CS) 13
semlock(CS) 14
semunlock(CS) 14
Ok(S) 14
Fail(S) 14
getfullname(CS) 15
CopyStoC(S) 15
GetTime(CS) 15
openfname(CS) 16
6 客户机函数 16
CTrans.h(C) 16
PktCtrl(C) 16
ClrMsg(C) 17
CInitRq(C) 17
ClntMsgGet(C) 17
WaitRes(C) 17
CopyCtoS(C) 18
7 守护进程 18
7.1 主机端 19
TranCtoSd 19
TranStoC 19
CopyCtoSd 19
7.2 客户端 19
TranCtoS 19
TranStoCd 19
CopyStoCd 19
8. 程序示例 20
8.1中心机端应用服务程序 20
8.2 客户机端应用程序 22
1 总体设计
通讯系统设计采用客户/服务器模式, 在PC 和主机两方各有三个守护进程, 负责在PC和主机之间传递业务请求和处理结果, 以及文件的相互传递。
PC和主机两方各有三个消息队列, 分别存放业务请求和处理结果。PC方的守护进程从业务请求消息队列中获得请求, 并发送给主机, 然后读取主机发回的结果并放入处理结果消息队列中。主机方的守护进程从通讯线路上读到业务请求, 并将其放入业务请求消息队列, 然后从结果消息队列中读取处理结果并将其传回PC方。另外, 处理结果队列用于主机主动向客户机发送处理结果, 报文处理与应答报文处理基本一致。下图是一个通讯流程简图。 对较长的返回内容分为多个报文传送.
主机方
┌──────┐
┌→│业务处理程序├─┬────────┐
│ └──────┘ │ │
请求│ │应答 结果│
│ ↓ ↓
┌───┴──┐ ┌──────┐ ┌──────┐
│业务请求队列│ │业务应答队列│ │处理结果队列│
└──────┘ └──┬───┘ └──┬───┘
请求↑ │应答 │结果
│ │ ↓
│┌───────┐ │ ┌───────┐
└┤主机守护进程一│←┘ │主机守护进程二│
└───────┘ └───┬───┘
↑│ │
请求││ │
------------------------------------------------------------------
││应答 │结果
│↓ ↓
┌──────┐ ┌──────┐
┌→│PC守护进程一├─┐ │PC守护进程二│
│ └──────┘ │ └──────┘
请求│ │应答 │
│ ↓ ↓结果
┌──┴───┐ ┌──────┐ ┌──────┐
│业务请求队列│ │业务应答队列│ │处理结果队列│
└──────┘ └──┬───┘ └──┬───┘
↑ │ │
请求│ │应答 │结果
│ ┌──────┐ │ │
└─┤客户机系统PC│←┘ │
│业务处理程序│←─────────┘
└──────┘
PC 方
特别地, 超长的返回内容采用文件拷贝的方式传输, 两端均有一守护进程用于处理文件的传输。
由于通讯线路发生故障或其它不明原因, 使客户机系统在向“发送消息队列”发送完消息队列后,从“接收消息队列”中读取返回结果时,超过一定时间期限还没读到返回结果。
这时客户机认为线路故障, 为此客户机系统提供一重发功能, 既将原先报文再次发送到“发送消息队列”中, 同以前报文所不同的是在该次报文头上加一“再送标志”。
主机接收到该报文后, 首先判断它是否为再送报文, 如果是再送报文, 首先到主机last目录中查询, 对应于该报文的报文流水号的交易是否已处理, 如已处理, 则直接将原先处理的结果发回, 不再进行处理, 如主机对该笔交易没有处理, 则主机系统重新处理该交易, 然后将处理的结果送回。
若报文种类定义为"0", 则不提供重发功能。
所有关于通讯的配置文件均存放在WPATH/com目录下,com目录包括三个子目录:
etc、log、last。具体内容将在下面详细说明。
2 报文结构
报文即为消息正文, 共分三类:
请求报文, 报文号以“0”开头;
应答报文, 报文号以“1”开头。
处理结果报文, 报文号以“1”开头。
每种交易都对应着一种报文格式, 组报和解报都是根据相应的报文格式完成的。
特殊报文号:
E00000: 由中心机发出, 表示请求报文为非法报文.
C00000: 由客户机发出, 用于清理无用报文.
2.1 请求报文格式
报文标志 (1) 0_正常 1_再送
发报机地址(名称) (10)
服务进程名称 (10)
报文流水号 (6)
营业所号 (7)
终端号 (2)
报文代号 (6)
操作员号 (4)
复核员号 (4)
数据正文 (不定长)
2.2 应答报文格式
报文标志 (1) 0_成功 1_失败
报文流水号 (6)
营业所号 (7)
终端号 (2)
报文代号 (6)
操作员号 (4)
复核员号 (4)
数据正文 (不定长)
2.3 处理结果报文格式
与应答报文格式一致.
3 电子日志格式定义
3.1 主机端日志
文件名:
WPATH/com/log/MsgLog
文件格式:
时间(YYYY-MM-DD HH:MM:SS) (20)
请求报文标志 (1)
请求报文流水号 (6)
营业所号 (7)
终端号 (2)
请求报文代号 (6)
操作员 (4)
复核员 (4)
返回报文标志 (1) 0_成功 1_失败
返回报文正文(部分) (100)
3.2 PC端日志
客户机系统同主机之间的每一笔交易不管成功与失败都要在电子日志中记录。
3.2.1 请求报文登记日志
文件名:
WPATH/com/log/RqLog
文件格式:
时间(YYYY-MM-DD HH:MM:SS) (20)
请求报文标志 (1)
客户机名称 (10)
服务进程名称 (10)
请求报文流水号 (6)
营业所号 (7)
终端号 (2)
请求报文代号 (6)
操作员 (4)
复核员 (4)
部分正文 (100)
3.2.2 结果日志
文件名:
WPATH/com/log/AnsLog
文件格式:
时间(YYYY-MM-DD HH:MM:SS) (20)
成功标志 (1)
应答交易流水号 (6)
营业所号 (7)
终端号 (2)
应答报文号 (6)
操作员 (4)
复核员 (4)
应答报文(部分) (100)
3.3 主机端最新返回报文登记
主机方对应客户机每一个当日发生业务的终端均保存最新的用于重发的返回报文, 存放到WPATH/com/last目录, 在服务器做日初处理时清空last目录.
文件名:
WPATH/com/last/所号_终端号.
文件格式:
请求报文流水号 (6)
成功标志 (1)
应答交易流水号 (6)
返回报文正文 (不定长)
4 配置文件格式
以下配置文件中所有字段以空格或制表符间隔.
4.1 服务进程定义
文件名:
WPATH/com/etc/servname
文件说明:
定义中心机应用服务进程及其代号。
文件格式:
服务进程名称(10)
代号
4.2 报文配置
文件名:
WPATH/com/etc/pktdef
文件说明:
定义每个交易报文的配置及业务备注。
文件格式:
报文代号 (6)
重送标志 (1) 0-不重送 1-需重送
重发时间(秒)
业务备注
4.3 当前流水号
文件名:
WPATH/com/etc/curlsh
文件说明:
存放下一个报文流水号,其中主机端用于应答报文及处理结果报文,客户机端用于请求报文 文件格式:
下一流水号(6)
4.4 监控屏幕定义
文件名:
WPATH/com/etc/console
文件说明:
定义服务进程及通讯系统运行信息监控屏幕。
文件格式:
设备名(缺省/dev/tty12)
4.5 中心机定义(仅用于客户端)
文件名:
WPATH/com/etc/servhost
文件说明:
在客户端定义中心主机名称。
文件格式:
机器名(10)
5 中心机函数说明
说明后用(CS)表示既可用于中心机, 也可用于客户机; 用(C)表示只用于客户方,用(S)表示只用于主机方。中心机函数包含在库STrans.a中。
下列函数均需 #include "STrans.h"
STrans.h(S)
#include <stdio.h>;
#include <varargs.h>;
#include <string.h>;
#include <fcntl.h>;
#include <signal.h>;
#include <errno.h>;
#include <math.h>;
#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/msg.h>;
#ifndef _TRANS_H_
#define _TRANS_H_
#define HRQID (key_t)3001 /* 请求队列 */
#define HANSID (key_t)3002 /* 应答队列 */
#define HRESID (key_t)3005 /* 主机主动发送队列 */
#define PutFlag 0 /* 拷贝文件到远地标志 */
#define GetFlag 1 /* 从远地拷贝文件标志 */
typedef struct { /* 请求报文结构 */
char bz[2]; /*报文标志 0_正常 1_再送 */
char addr[11]; /*发报机器名称 */
char serv[11]; /*服务器种类 */
long lsh; /*报文流水号 */
char sh[8]; /*所号 */
char zdh[3]; /*终端号 */
char bwdh[7]; /*报文代号 */
char czydh[5]; /*操作员 */
char fhydh[5]; /*操作员 */
char text[4096]; /*数据正文 */
} prq_t;
typedef struct { /* 应答报文结构 */
char bz[2]; /*报文标志 0_成功 1_失败 由应用程序赋值 */
long lsh; /*交易流水号 */
char sh[8]; /*所号 */
char zdh[3]; /*终端号 */
char bwdh[7]; /*报文代号 */
char czydh[5]; /*操作员 */
char fhydh[5]; /*操作员 */
char text[4096]; /*正文, 由应用程序赋值,若长度不够, 需另分配空间 */
}pans_t;
/* 交易函数派遣表, 在说明为数组时, 赋值时必须以{"",0}结束, 如:
int ex1(), ex2();
term_t stab[] = {
{"000001", ex1},
{"000002", ex2},
...
...
{"", 0}
};
*/
typedef struct {
char dh[7]; /* 报文代号 */
int (*func)(); /* 对应该报文的函数,格式: (*func)(prq_t *rq, pans_t *ans)*/
} term_t;
typedef struct {
long type; /* 消息类型 */
char text[4096]; /* 消息正文 */
} msg_t;
int timeout; /* 等候超时标志 (-1)超时 */
int rq_id; /* 请求队列标识号 */
int ans_id; /* 应答队列标识号 */
int res_id; /* 主机主动发送队列标识号 */
long CltType; /* 用于客户机的消息类型, CltType=atol(prq.sh)*100+atoi(prq.zdh)*/
long ServType; /* 用于中心机接收的消息类型, 取自$WPATH/com/etc/servname */
char errnu[81]; /* 错误说明, 在出现错误时赋值, 应用程序可在发生错误时显示 */
/* 函数类型说明 */
char *malloc(), *realloc();
char *getenv();
FILE *openfname();
long GetServType();
long getlsh();
long atol();
#endif /* _TRANS_H_ */
● ServCtrl(S)
[语法] void ServCtrl(stab, servname)
term_t stab[];
char *servname;
[说明] 等候请求, 根据stab, 调用相应函数, 发送应答, 处理报文重发. servname为 当前服务当前进程
[返回值] 无
● SendAns(S)
[语法] int SendAns(rq, ans)
prq_t *rq;
pans_t *ans;
[说明] 发送应答报文, 同时登记主机端日志, rq为请求报文, ans为应答报文.
[返回值] 成功返回0, 失败返回-1.
● SendRes(S)
[语法] int SendRes(pktnum, ans, HostName)
char *pktnum, *HostName;
pans_t *ans;
[说明] 发送处理结果报文ans, 报文代号pktnum, 同时登记主机端日志.
[返回值] 成功返回0, 失败返回-1.
● analtext(CS)
[语法] int analtext(buf, fmt, va_alist)
char *buf, *frm;
va_dcl
[说明] 分析buf, 按fmt定义的长度及类型放入va_alist指定的参数表中. fmt的格式
如"%
[n]T...", T可取:
c:字符
d:整型
ld,:长整型
f:浮点型
lf:双精度浮点型
s:字符串
fmt的第一个字符必须是"%", 每个格式定义中间可用空格分开, 以利阅读. 在格式符前加"*", 表示忽略该字段.格式符前必须加长度定义, buf的最后一个字段若为字符串, 可不用长度定义. buf的结束符为'\0', 其他如'\n','\r'不做为结束符.
[返回值] 返回实际得到的数据个数.
● danaltext(CS)
[语法] int danaltext(buf, del, fmt, va_alist)
char *buf, *fmt, del;
va_dcl
[说明] 分析buf, del为字段分隔符, 按fmt定义的长度及类型放入va_alist指定的参数表中. fmt的格式如"%
[n]T...", T可取c,d,ld,f,lf,s, fmt的第一个字符必须是
"%", 每个格式定义中间可用空格分开, 以利阅读. 在格式符前加"*", 表示忽略该字段. 格式符"s"前若加长度定义, 返回的串定长为n. buf的结束符为'\0', 其他如'\n','\r'不做为结束符. 在buf的字段中若出现与del相同的字符, 应用程序必须在该字符前加'\', 以示区别, 否则将导至出错.
[返回值] 返回实际得到的字段数据个数.
● GetPktNote(CS)
[语法] int GetPktNote(pkt_num, note)
char *pkt_num, *note;
[说明] 从WPATH/com/etc/pktdef中取报文pkt_num的注释note.
[返回值] 文件不存在返回-1, 其余返回0.
● OutputOnCon(CS)
[语法] int OutputOnCon(fmt, va_alist)
char *fmt;
va_dcl
[说明] 向监控屏幕写监控信息(监控屏幕定义在WPATH/com/etc/console).
[返回值] 成功返回0, 失败返回-1.
● semlock(CS)
[语法] int semlock(sem)
char *sem;
[说明] 锁文件WPATH/sem.
[返回值] 返回WPATH/sem文件标识符.
● semunlock(CS)
[语法] void semunlock(fd)
int fd;
[说明] 关闭文件标识fd.
[返回值] 无
● Ok(S)
[语法] int Ok(rq, ans, fmt, va_alist)
prq_t *rq;
pans_t *ans;
char *fmt;
va_dcl
[说明] Ok根据rq, fmt, va_alist发送成功报文ans. 报文不能超过4096字节。
[返回值] 无
● Fail(S)
[语法] int Fail(rq, ans, fmt, va_alist)
prq_t *rq;
pans_t *ans;
char *fmt;
va_dcl
[说明] Fail根据rq, fmt, va_alist发送失败报文ans. 报文不能超过4096字节。
[返回值] 无
● getfullname(CS)
[语法] void getfullname(pth, fmt, va_alist)
char *pth, *fmt;
va_dcl
[说明] 取全路径名WPATH/(fmt, va_alist), 赋与pth.
[返回值] 无
● CopyStoC(S)
[语法] int CopyStoC(Flag, Local-file, Remote-file, HostName)
int Flag;
char *Local-file, *Remote-file, *HostName;
[说明] Flag = 0: 将本地文件Local-file拷贝到主机HostName, 文件名为Remote-file Flag = 1: 将主机HostName文件Remote-file拷贝到本地, 文件名为Local-file
[返回值] 成功返回0, 失败返回-1.
● GetTime(CS)
[语法] void GetTime(cur_time)
char *cur_time;
[说明] 取当前系统时间, 格式YYYY-MM-DD HH:MM:SS.
[返回值] 无
● openfname(CS)
[语法] FILE *openfname(type, fmt, va_alist)
char *type, *fmt;
va_dcl
[说明] 以type方式打开文件WPATH/(fmt, va_alist).
[返回值] 返回文件指针, 失败返回NULL.
6 客户机函数
所有客户机的函数都包含在库CTrans.a中。下列函数均需 #include "CTrans.h"
● CTrans.h(C)
在此仅说明与中心机系统STrans.h不同的部分.
#define PRQID (key_t)3003 /* 请求队列 */
#define PANSID (key_t)3004 /* 应答队列 */
#define PRESID (key_t)3006 /* 主机主动发送队列 */
int MaxTime; /* 当前交易最长等候时间 */
● PktCtrl(C)
[语法] int PktCtrl(pktnum, servname, rq, ans)
char *pktnum, *servname;
prq_t *rq;
pans_t *ans;
[说明] 控制报文收发. 处理报文重发, 自动登记电子日志. pktnum为报文代号, servname为服务进程名.
[返回值] 成功返回0, 失败返回-1.
● ClrMsg(C)
[语法] int ClrMsg()
[说明] 清理无用报文.
[返回值] 成功返回0, 失败返回-1.
● CInitRq(C)
[语法] CInitRq(rq, sh, zdh, czydh, fhydh)
prq_t *rq;
char *sh, *zdh, *czydh, *fhydh;
[说明] 设定请求报文中比较固定的字段(包括所号、终端号、操作员代号、复核员代号),可在程序的开始调用。
● ClntMsgGet(C)
[语法] int ClntMsgGet()
[说明] 获得终端消息队列
[返回值] 成功返回0, 失败返回-1.
● WaitRes(C)
[语法] int WaitRes(pktnum, ans)
char *pktnum;
pans_t *ans;
[说明] 等候主机主动发回的处理结果报文. pktnum为报文代号. 收到报文后,分析报文,存 放在ans, 并登记日志AnsLog.
[返回值] 成功返回0, 失败返回-1.
● CopyCtoS(C)
[语法] int CopyCtoS(Flag, Local-file, Remote-file)
int Flag;
char *Local-file, *Remote-file;
[说明] Flag = 0: 将本地文件Local-file拷贝到中心主机, 文件名为Remote-fileFlag = 1: 将中心主机文件Remote-file拷贝到本地, 文件名为Local-file
[返回值] 成功返回0, 失败返回-1.
7 守护进程
以下守护进程均需要事前定义环境变量WPATH
7.1 主机端
启动顺序为TranCtoSd, TranStoC, CopyCtoSd, 应用服务进程.
● TranCtoSd
接收客户机的请求报文并放入请求队列, 从应答队列接收并发送应用进程的应答报文.
● TranStoC
从处理结果队列接收并转发处理结果报文.
● CopyCtoSd
根据客户端的请求, 收发文件.
7.2 客户端
启动顺序为TranCtoS, TranStoCd, CopyStoCd, 应用服务进程.
● TranCtoS
从请求队列接收并转发请求报文, 接收中心机应用进程的应答报文放入应答队列.
● TranStoCd
接收中心机应用进程的处理结果报文放入处理结果队列.
● CopyStoCd
8. 程序示例
8.1中心机端应用服务程序
● 编译:
cc demoserv.c s.a -o demoserv -lsocket
● 程序:
/* demoserv.c */
#include "STrans.h"
/* 定义交易处理函数 */
int ex1(), ex2();
term_t stab[]= {
{"000001", ex1},
{"000002", ex2},
{"", 0},
};
main()
{
ServCtrl(stab, "demoserv");
}
ex1(rq, ans)
prq_t *rq;
pans_t *ans;
{
char buf[100],note[30];
/* 根据rq进行处理 */
/*
...
...
*/
if (...) {
Fail(rq, ans, "数据错误");
return 1;
}
/*
.
.
*/
strcpy(buf, "1234567890");
strcpy(note, "交易成功");
/* 发回成功报文 */
Ok(rq, ans, "%-15s%-20s", buf, note);
/* 处理数据 */
.
.
*/
strcpy(ans->;text, "ABCDEFGH");
/* 主动发回处理结果 */
SendRes("100006", ans, rq->;addr);
/* 拷贝文件,rq->;addr是请求抱文中的客户机名称 */
CopyStoC(PutFlag, "/tmp/file1", "/tmp/file1", rq->;addr);
CopyStoC(GetFlag, "/tmp/file2", "/tmp/file2", rq->;addr);
}
ex2(rq, ans)
prq_t *rq;
pans_t *ans;
{
char buf[30000];
char p[10];
int i;
/* 交易处理,产生应答报文 */
ans->;text[0] = 0;
for (i = 0;i< 5000;i++) {
sprintf(p, "%04d\n", i);
strcat(ans->;text, p);
}
strcpy(ans->;bz, "0"); /* 置应答报文成功标志 */
SendAns(rq, ans);
}
/* The end */
8.2 客户机端应用程序
● 编译:
cc democlnt.c CTrans.a -o democlnt -lsocket
#include "CTrans.h"
main()
{
ex();
}
ex()
{
pans_t pans;
prq_t prq;
/* 取得消息队列 */
ClntMsgGet();
/* 定义请求报文*/
CInitRq(&prq, "1234567", "12", "1234", "ABCD");
strcpy(prq.text, "0");
/* 进行交易000010 */
PktCtrl("000010", "demoserv", &prq, &pans);
printf("text=%s\n", pans.text);
/* 等待接收主机主动发回处理结果报文 ,报文代号为100060 */
WaitRes("100060" , &pans);
printf("text=%s\n", pans.text);
getchar();
strcpy(prq.text, "1");
PktCtrl("000020", "demoserv", &prq, &pans);
printf("text=%s\n", pans.text);
/* 拷贝文件 */
CopyCtoS(PutFlag, "/tmp/demo1", "/tmp/demo1");
CopyCtoS(GetFlag, "/tmp/demo2", "/tmp/demo2");
}