msgparse接口使用说明

本章介绍如何使用 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。

表13.1 type返回
输出消息报文的宏定义 输出消息报文的含义 类型值
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 的有效空间,输出时为构建响应数据的真实长度。

表13.2 部分错误码
错误码 错误描述 宏定义
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;
}
微信扫码
分享文档
扫一扫
联系客服