本章介绍如何使用 msgparse 接口。dmmsgparse.dll 是达梦数据库的消息解析工具的 C 接口,供应用程序直接调用。dmmsgparse.dll C 接口依赖的动态链接库在 DM 安装目录\bin 目录下,依赖的静态库 dmmsgparse.lib 在 DM 安装目录\include 目录下,所需的头文件 dmmsgparse_pub.h 也在 DM 安装目录\include 目录下。
13.1 接口说明
提供一个动态库 dmmsgparse.dll/dmmsgparse.lib,内含 7 个函数。函数声明及说明如下。
13.1.1 获取请求数据包类型
函数原型
int ReqType(
unsigned char* buf, int buflen, int* type);
参数说明
buf:输入参数,通讯包首地址。
buflen: 输入参数,数据长度。
type: 输出参数,以 *type 返回数据包类型,需给出具体类型说明。type 返回内容见下表 13.1。
输出消息报文的宏定义 | 输出消息报文的含义 | 类型值 |
---|---|---|
DM_MSG_LOGIN | 登录消息 | 101 |
DM_MSG_LOGOUT | 登出消息 | 102 |
DM_MSG_STMT_ALLOCATE | 分配语句句柄 | 103 |
DM_MSG_STMT_FREE | 语句句柄释放 | 104 |
DM_MSG_PREPARE | 准备语句 | 105 |
DM_MSG_FETCH | 获取结果集 | 106 |
DM_MSG_COMMIT | 提交 | 107 |
DM_MSG_ROLLBACK | 回滚 | 108 |
DM_MSG_CANCLE | 取消 | 109 |
DM_MSG_EXECUTE2 | 绑定参数 | 110 |
DM_MSG_PUT_DATA | 写入大字段数据 | 111 |
DM_MSG_GET_DATA | 获取数据请求 | 112 |
DM_MSG_CREATE_BLOB | 创建大字段 | 113 |
DM_MSG_CLOSE_STMT | 关闭语句句柄 | 114 |
DM_MSG_TIME_OUT | 超时 | 115 |
DM_MSG_CURSOR_PREPARE | 准备游标 | 116 |
DM_MSG_CURSOR_EXECUTE | 执行游标 | 117 |
DM_MSG_EXPLAIN | 查询计划 | 118 |
DM_MSG_GET_DATA_ARR | 获取一组大字段的完整数据 | 119 |
DM_MSG_GET_ROW_NUM | 获取行数 | 120 |
DM_MSG_GET_LOB_LEN | 获取大字段长度 | 121 |
DM_MSG_GET_LOB_DATA | 获取大字段数据 | 122 |
DM_MSG_MORE_RESULT | 获取更多结果集 | 123 |
DM_MSG_EXEC_RETURN_AUTO_VALUE | 获取 return into 值 | 124 |
DM_MSG_RESET_SESS | 重置会话 | 125 |
DM_MSG_PRE_EXEC | DM_MSG_PRE_EXEC 是 DM_MSG_EXECUTE 中 PUT_DATA 时生成计划所需的命令字 | 126 |
DM_MSG_EXEC_DIRECT | 常量参数化执行 sql | 127 |
DM_MSG_STARTUP | 启动 | 128 |
功能说明
对消息解析后返回消息类型。
返回值
0:成功;
<0:错误。
13.1.2 检测响应数据是否为登录成功响应
函数原型
int IsLogined(unsigned char* buf, int buflen);
参数说明
buf:通讯包首地址。
buflen:数据长度。
功能说明
判断消息是否为登录成功响应。
返回值
1:是。
0:否,表示非登录成功响应,包括不能识别或错误的数据。
13.1.3 检测请求数据是否含有完整包长
函数原型
int HaveAFullReqPack(
unsigned char* buf, int buflen, int* pPackLen);
参数说明
buf:输入参数,通讯包首地址。
buflen: 输入参数,数据长度。
pPackLen: 输出参数,包含完整的数据包时,以*pPackLen 返回第一个完整数据包的长度,长度为包含协议头的总长度
功能说明
判断消息是否有完整包长。pPackLen 不为 NULL 时,指针返回消息的总长度=数据包的消息头 + 数据包的消息体的长度。为 NULL 则不返回。
返回值
0:是;
<0:否,或发生错误。
13.1.4 检测请求数据是否为新 SQL 指令
函数原型
int IsNewSQL(unsigned char* buf, int buflen);
参数说明
buf:通讯包首地址。
buflen:数据长度。
功能说明
所谓新 SQL 指令,通常是指请求数据为一次新的 SQL 请求。
相对的,比如一个 SQL 查询,结果集数量比较多,从而从通讯看看,存在多次收发数据,此时不应认为是新 SQL;
消息类型是 CMD_PREPARE、CMD_EXEC_DIRECT、CMD_EXECUTE2(一次 prepare, 绑定不同参数 execute)都是第一次 SQL 请求,返回 0。
返回值
0:是;
<0:否,或发生错误。
13.1.5 构建错误应答报文
函数原型
int CrtErrResp(int errno, char* errmsg, unsigned char* respbuf, int* respbuflen);
参数说明
errno:输入参数,错误码,预定义几种错误码(需补充已知常用的错误码),用于映射为达梦标准错误码。已知常用的错误码包括成功、动态库内部错误码、异常连接错误码。部分错误码见下表 13.2。
errmsg:输入参数,错误信息。以 '\0' 为结束符的 C 语言形式的 ANSI 编码的错误消息描述;如果允许,则用于填充错误内容。如果不标准,则自动更正成标准错误码后再填充错误内容。
respbuflen:输入时为 respbuf 的有效空间,输出时为构建响应数据的真实长度。
错误码 | 错误描述 | 宏定义 |
---|---|---|
0 | "Ec success." | DM_EC_SUCCESS |
-1【消息长度不够】 -2【数据无法识别】 -3【非法消息】 |
"Invalid message." | DM_INVALID_LEN DM_WR_MSG DM_INVALID_MSG_TYPE |
-5【缓冲区长度过小】 | "message size is out of buffer." | DM_OUT_OF_MSG_BUFF |
-11【客户端版本错误】 | Invalid client version. | DM_EC_INVALID_LEN |
-12【UKEY 认证失败】 | UKEY authentication mismatch. | DM_EC_UKEY_AUTH_MISMATCH |
-13【证书验证失败】 | Fail to establish connection. | DM_EC_CONNECT_CAN_NOT_ESTABLISHED |
-14【无效的用户名】 | Invalid user name | DM_EC_RN_INVALID_USER_NAME |
-15【解密失败】 | fail to decrypt | DM_EC_ENC_DECRYPT_FAIL |
-16【口令失败】 | Invalid password policy | DM_EC_INVALID_PASSWORD |
-17【请求执行过程中连接断开】 | Connection exception | DM_EC_RECV_OOB |
-18【连接失败】 | Connection lost | DM_EC_CONNECT_LOST |
功能说明
此方法用于拦截异常连接,构建满足达梦标准的响应包,以错误形式通知到异常连接的客户端。
由于不能预先获知构建报文所需的实际大小,拟当输入参数 respbuf 为 NULL 时,方法计算出所需空间,通过 * respbuflen 返回;
调用者申请空间后,以非 NULL 的 respbuf 再次调用此方法,同时用 * respbuflen 给出空间长度,如空间长度足够时,真正生成报文。
消息体填写英文错误描述。不设置消息体中的 crc。(自行发送时设置)。
返回值
0:成功;
<0:错误。
13.1.6 提取请求数据中的 SQL 和参数
函数原型
int ParseReq(
unsigned char* buf, int buflen, char* sql, int* sqllen, PARAMINFO* pParams, int* iParam);
参数说明
buf:输入参数,通讯包首地址
buflen:输入参数,数据长度
sql:输出参数,以*sql 返回解析后的 SQL 语句;当 sql 为 NULL 时,不真正解析,而是通过*sqllen 返回所需空间长度,以及用*iParam 返回可能的参数个数
sqllen: 输入时为 sql 的有效空间,输出时为 sql 数据的真实长度。
pParams:输出参数,以 pParams 为首地址,返回 iParam 个参数信息。PARAMINFO 的定义为:
typedef struct {
int serno; // 参数序号
int type; // 用整数表示的参数类型,如:0,整数; 1,浮点数; 2:字符串; 3:二进制数据 等等
char name[256]; // 参数名称
void* val; // 参数内容,放在*val
int vallen; // 参数内容有效长度.
} PARAMINFO;
iParam:输入时为 pParams 的有效个数,输出时为 pParams 的实际个数。
功能说明
提取请求数据中的 SQL 和参数。
有以下几种情况:
1)sql 为 NULL 的情况下:
消息是 CMD_PREPARE 类型的只返回 SQL 长度*sqllen, CMD_EXEC_DIRECT 类型的返回 SQL 长度*sqllen。
2)pParams 为 NULL 的情况下:
消息是 CMD_EXECUTE2 类型的只返回参数个数*iParam。CMD_EXEC_DIRECT 类型的返回参数个数*iParam。
3)sql 不为 NULL 的情况下:
消息是 CMD_PREPARE 则只返回 sql 语句串。CMD_EXEC_DIRECT 类型返回 sql 语句串。pParams 不为 NULL, iParam 也不返回。
4)pParams 不为 NULL 的情况下:sql 不为 NULL,sqllen 也不返回。
消息是 CMD_PRE_EXEC 只填参数 PARAMINFO 部分信息(名称、序号、类型)、参数长度 0 内容不填。
消息是 CMD_PUT_DATA 因为只有 clob 控制头信息,包括行外地址。只填参数 PARAMINFO 部分信息(类型为大字段,序号,名称不填),参数长度为实际字节个数、内容不填。CMD_PUT_DATA 之后的 CMD_EXECUTE2 消息,只填参数 PARAMINFO 部分信息(类型为大字段,序号), 其他信息-1,参数长度-1 和内容不填。
消息是 CMD_EXECUTE2 类型则只填参数 PARAMINFO 信息(名称、序号、类型)、 参数长度、 参数内容。(消息是 CMD_EXEC_DIRECT,有可能有参数,参数还有可能是常量参数化信息。CLT_CONST_TO_PARAM 开关打开,非绑定列、绑定参数的 sql 都进 exec_direct。不带常量就没有参数。)参数长度-2 表示 NULL。参数类型为-2 表示输出参数。消息是 CMD_EXEC_DIRECT 类型也填上述参数信息。复合类型(ARRAY/ CLASS /RECORD/SARRAY)/游标类型 CURSOR/罕见类型 的参数数据暂不获取。参数长度是实际长度,内容不填。多行批量执行的只取第一行参数内容。
参数描述是从序号 1 开始的。参数名用英文返回。参数类型支持:CHAR/VARCHAR2/ VARCHAR/BIT/TINYINT/SMALLINT/INT/INT64/FLOAT/DOUBLE/BINARY/ VARBINARY/BLOB/TEXT/INTERVAL_YM/INTERVAL_DT/ROWID/XDEC/DATE/TIME/DATETIME/TIME_TZ/DATETIME_TZ/复合类型/游标类型/罕见类型。不支持的参数类型返回-7。下表为支持的参数类型。
返回值
0:成功;
<0:错误。
13.1.7 展示可解析的消息格式版本
函数原型
Int GetMsgVer()
参数说明
无
功能说明
获取消息格式的当前版本号
返回值
可解析的消息格式的当前版本号(固定为当前最新值 9),宏定义为 DM_COMM_VER 9。
13.2 基本示例
开启服务器和客户端/接口之间通信的抓包工具不作介绍,这里只介绍测试环境。
windows 环境配置:
1.配置静态库
本工程采用静态库(dmmsgparse.lib)加载的方式。
需要将静态放置到 dm_cmpp_test\dm_cmpp_test\WorkingDir\目录下。
在此工程 WorkingDir 目录下已有一份生成好的。
2.配置头文件
本工程需要用到头文件 msgparse_pub.h。请将将头文件 msgparse_pub.h 放置到 dm_cmpp_test\include 目录下。
此工程 include 目录下已有一份放置好的。
3.编译
VS2010 工程中,右键项目名称,点击生成。
例
#include "msgparse_pub.h"
#include <Windows.h>
#include <stdio.h>
#pragma comment(lib, "dmmsgparse.lib")
// 变量声明
//HINSTANCE msgdll;
const schar* proc_name;
lint type, ret;
dmcode_t code = 0;
byte* respbuf;
lint respbuflen = 32767;
lint pPackLen;
char* sql = NULL; // INOUT:返回解析后的SQL语句
int sqllen; // INOUT:输入时为sql的有效空间,输出时为sql数据的真实长度
PARAMINFO* pParams = NULL; // INOUT:iParam个参数的信息存储空间
int iParam; //INOUT:输入时为pParams的有效个数, 输出时为pParams的实际个数
int i;
// 检测响应数据是否是登陆成功 isLogined
int test_isLogined(unsigned char *buf, int len){
int ret;
ret = IsLogined(buf, len);
if(ret==0){
printf("logined: false\n");
}
else if (ret == 1){
printf("logined: true\n");
}
else{
printf("logined: ret = %d, it's maybe a bug.\n", ret);
}
return ret;
}
// 测试ReqType,获取消息类型
int test_ReqType(unsigned char *buf, int len){
int type, ret;
ret = ReqType(buf, len, &type);
if(ret != 0){
switch(ret) {
case DM_INVALID_LEN:
printf("ReqType:Invalid msg lenth\n");
break;
case DM_WR_MSG:
printf("ReqType:Wrong message.\n");
break;
case DM_INVALID_MSG_TYPE:
printf("ReqType:Invalid message type.\n");
break;
default:
printf("ReqType:Really really error, it's maybe a bug\n");
break;
}
return ret;
} else {
if(type>999){
printf("definitely a bug: type = %d\n", type);
}
printf("type = %d\n", type);
}
return ret;
}
// 检测请求数据是否含有完整包长 HaveAFullReqPack
int test_HaveAFullReqPack(unsigned char *buf, int len, int pPackLen){
int ret;
ret = HaveAFullReqPack(buf, len, &pPackLen);//pPackLen
if(ret != 0){
printf("test_HaveAFullReqPack error, can't get packLen\n");
return ret;
} else {
printf("pPackLen = %d\n", pPackLen);
}
return ret;
}
// 检测请求数据是否为新SQL指令IsNewSQL
int test_isNewSQL(unsigned char *buf, int len){
int ret;
ret = IsNewSQL(buf, len);
switch(ret) {
case DM_INVALID_LEN:
printf("isNewSQL:Invalid msg lenth\n");
break;
case DM_WR_MSG:
printf("isNewSQL:Wrong message.\n");
break;
case DM_INVALID_MSG_TYPE:
printf("isNewSQL:Invalid message type.\n");
break;
case DM_RN_NEW_SQL:
printf("isNewSQL:Is not new sql.\n");
break;
case DM_EC_SUCCESS:
printf("isNewSQL:Ec success.\n");
break;
default:
printf("isNewSQL:Really really error, it's maybe a bug\n");
break;
}
return ret;
}
// 构建错误应答报文CrtErrResp
byte* test_crtErrResp(int i){
int ret;
respbuf = NULL;
ret = CrtErrResp(i, "", respbuf, &respbuflen);
if(ret != 0)
{
if(ret<0)
{
printf("crtErrResp: ret<0, It' OK\n");
}
else{
printf("crtErrResp error, it's maybe a bug\n");
}
} else {
printf("respbuflen = %d\n", respbuflen);
// 再次调用,利用respbuflen构造respbuf
respbuf = (byte*)malloc(respbuflen);
ret = CrtErrResp(i, "message size is out of buffer.", respbuf, &respbuflen);
}
return respbuf;
}
// 提取请求数据中的SQL和参数ParseReq
int test_ParseReq(unsigned char *buf, int len){
int ret, code, i;
// 第一次调用之前,sql/sqllen/pParams,都要给NULL或0,这样ParseReq函数第一次调用会返回sqllen和iParam,用户自己利用sqllen和iParam给sql和pParams分配空间
sql = NULL;
sqllen = 0;
pParams = NULL;
ret = ParseReq(buf, len, sql, &sqllen, pParams, &iParam);
if (ret == 0)
{
if (sqllen > 0)
{
sql = (char*)malloc(sqllen);
}
if (iParam > 0)
pParams = (PARAMINFO*)malloc(sizeof(PARAMINFO) * iParam);
//再次调用 解析sql和参数信息
ret = ParseReq(buf, len, sql, &sqllen, pParams, &iParam);//获取iParam,pParams
if (ret == 0)
{
code = DM_EC_INVALID_MSG;
if (sqllen > 0){
printf("sql = %s\n", sql);
free(sql);
}
if (iParam > 0){
printf("paramCount = %d\n", iParam);
for(i=0; i<iParam; i++){
printf("pParams->type = %d\n", pParams[i].type);
printf("pParams->name = %s\n", pParams[i].name);
printf("pParams->lenth = %d\n", pParams[i].vallen);
switch(pParams[i].type){
case 1:
printf("pParams->val = %s\n", pParams[i].val.str);
break;
case 2:
case 3:
case 4:
printf("pParams->val = %d\n", pParams[i].val.int_val);
break;
case 5:
printf("pParams->val = %lld\n", pParams[i].val.int64_val);
break;
case 6:
printf("pParams->val = %f\n", pParams[i].val.float_val);
break;
case 7:
printf("pParams->val = %lf\n", pParams[i].val.double_val);
break;
case 8:
printf("pParams->val = %s\n", pParams[i].val.bin_val);
break;
case 9:
printf("pParams->val = %s\n", pParams[i].val.lob_val);
break;
case 10:
case 11:
printf("pParams->val = %s\n", pParams[i].val.intv_str);
break;
case 12:
printf("pParams->val = %s\n", pParams[i].val.rowidstr);
break;
case 13:
printf("pParams->val = %s\n", pParams[i].val.xdec_str);
break;
case 14:
case 15:
case 16:
case 17:
case 18:
printf("pParams->val = %s\n", pParams[i].val.dt_str);
break;
default:
break;
}
printf("\n");
}
free(pParams);
}
}
else{
printf("test_ParseReq:ret = %d\n", ret);
if (sqllen > 0){
printf("sql = %s\n", sql);
free(sql);
}
if (iParam > 0){
printf("paramCount = %d\n", iParam);
free(pParams);
}
}
}
else if (ret != DM_NO_SQL_OR_PARAM) // 消息报错返回不是-6
{
code = DM_EC_INVALID_MSG;
printf("test_ParseReq:ret = %d\n", ret);
printf("test_ParseReq: type is in (126)/(111)/(105)/(110)/(127), is type right?\n");
}
else{ // 直接打印报错
printf("test_ParseReq:ret = %d\n", ret);
}
return code;
}
// 展示可解析的消息格式版
int test_GetMsgVer(){
int ret;
ret = GetMsgVer();
if(ret != 10){
printf("GetMsgVer error: %d\n", ret);
}
return ret;
}
// 主函数
int main()
{
// 这里放置测试程序截获的客户端发给服务器的消息
// 此测例中的消息使用wireshark抓包工具获取,包头中带有其他信息,真正的消息从偏移54位开始
static const unsigned char pkt6[162] = {
0xe0, 0x24, 0x7f, 0xc3, 0x90, 0xa2, 0xa4, 0xae, /* .$...... */
0x12, 0x2a, 0x6e, 0xb4, 0x08, 0x00, 0x45, 0x00, /* .*n...E. */
0x00, 0x94, 0x6e, 0x4e, 0x40, 0x00, 0x80, 0x06, /* ..nN@... */
0x00, 0x00, 0xc0, 0xa8, 0x01, 0x27, 0xc0, 0xa8, /* .....'.. */
0x64, 0x76, 0xfb, 0x72, 0x18, 0x74, 0xc0, 0x12, /* dv.r.t.. */
0x5b, 0xae, 0x87, 0xc6, 0x32, 0xd7, 0x50, 0x18, /* [...2.P. */
0x01, 0xfd, 0xe7, 0x74, 0x00, 0x00, 0x00, 0x00, /* ...t.... */
0x00, 0x00, 0x05, 0x00, 0x2c, 0x00, 0x00, 0x00, /* ....,... */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ........ */
0x00, 0x29, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, /* .)...... */
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* ........ */
0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ........ */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ........ */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ........ */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6e, /* ......in */
0x73, 0x65, 0x72, 0x74, 0x20, 0x69, 0x6e, 0x74, /* sert int */
0x6f, 0x20, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, /* o test_c */
0x68, 0x28, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x29, /* h(value) */
0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x28, /* values( */
0x27, 0x3a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x27, /* ':value' */
0x29, 0x00 /* ). */
};
int len = sizeof(pkt6);
byte* errorMessage = NULL;
// 展示可解析的消息格式版
test_GetMsgVer();
// 判断消息是否是登录消息
test_isLogined((unsigned char*)pkt6+54, len-54);
// 获取消息类型
test_ReqType((unsigned char*)pkt6+54, len-54);
// 判断消息是否含有是完整的包长
test_HaveAFullReqPack((unsigned char*)pkt6+54, len-54, pPackLen);
// 判断消息是否是一次新的sql请求
test_isNewSQL((unsigned char*)pkt6+54, len-54);
// 解析消息中的sql与参数信息
test_ParseReq((unsigned char*)pkt6+54, len-54);
// 构建错误应答报文
errorMessage = test_crtErrResp(DM_EC_UKEY_AUTH_MISMATCH);
return code;
}