分享
 
 
 

C++扩展存储编程--使用UDP发送数据库的更新通知

王朝c/c++·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

关键字:VC++, SQL Server,UDP,扩展存储过程

作者:邓杨均

EMail: dyj057@gmail.com

当数据库更新的时候,如果插入,删除,修改了数据记录,我们的应用程序需要知道这

些记录,就需要使用触发器,触发器再调用扩展存储过程,扩展存储过程再把数据使用

UDP的通讯方式,发送到应用程序,这样应用程序就会收到数据库更新的通知。

1. 创建扩展存储过程的工程

启动Visual Studio 2003 -> 新建Visual C++项目 -> 扩展存储过程DLL

2.在生成的向导中,输入你要创建的扩展存储过程的名称,这个很重要,一开始就要认真填写,

否则工程创建完成后,就不好修改了.在我们例子里面就输入xp_door_web_site

3.读取存储过程的参数,然后形成数据报文,再发送出去,该示例存储过程有4个参数.有varchar,int等数据类型.

先来看看主要的处理流程:

RETCODE __declspec(dllexport) xp_door_web_site(SRV_PROC *srvproc)

{

XPLib lib(srvproc); //Create tool class

try

{

DWSEvent newEvent; // Create a new event

//Check paramerter's number

if (srv_rpcparams(srvproc) != 4)

{

// Send usage message to client and return error

lib.printUsage("xp_door_web_site <UserId>,<Url>,<Message>,<type>");

return (XP_ERROR);

}

lib.readVarcharPara(1,&newEvent.m_strUserId[0], (int)newEvent.m_strUserId.capacity(), false,"UserID");

lib.readVarcharPara(2,&newEvent.m_strUrl[0], (int)newEvent.m_strUrl.capacity(), false,"Url");

lib.readVarcharPara(3,&newEvent.m_strMsg[0], (int)newEvent.m_strMsg.capacity(), false,"Msg");

lib.readIntPara(4,newEvent.m_nType, false,"Type");

newEvent.SetConfigFile("C:\\XPdoorwebsite.config.ini");

newEvent.Cast();

}

catch(CExceptionEx & me)

{

lib.printError( me.what() );

return XP_ERROR;

}

catch(...)

{

lib.printError("Unkowned exception occured");

return XP_ERROR;

}

return XP_NOERROR ;

}

代码都很容易理解,也很简单,就不多言

4. XPLib类库是我自己写的,是编写扩展存储过程主要的可复用的类,它的职能是读取存储过程的参数,和打印错误和使用信息.

XPlib.h的声明如下:

#pragma once

#include <string>

using namespace std;

namespace IBMS

{

namespace Data

{

class XPLib

{

public:

XPLib(SRV_PROC *pSrvProc);

~XPLib(void);

static const int MAX_SERVER_ERROR = 20000;

static const int XP_SEND_TO_IBMS_ERROR = MAX_SERVER_ERROR + 1;

public:

void printUsage (const char * usage);

void printError (const char * errroMsg);

void readIntPara( int nParaIndex, int &iValue, bool bAllowNull = true, string paraName="");

void readVarcharPara(int nParaIndex, char * szValue, int nMaxLength=-1, bool bAllowNull = true,string paraName="");

private:

SRV_PROC * m_pSrvProc;

};

}

}

XPlib.cpp的实现如下:

#include "StdAfx.h"

#include ".\xplib.h"

#include <cassert>

#include <sstream>

using namespace std;

#include "moreexception.h"

using namespace IBMS::StdLib;

namespace IBMS

{

namespace Data

{

/****************************************************************************

*Class:

XPLib

*Function:

XPLib(SRV_PROC *pSrvProc)

*Purpose:

construct the class

*Parameter:

[in]pSrvProc

*Return:

****************************************************************************/

XPLib::XPLib(SRV_PROC *pSrvProc)

:m_pSrvProc(pSrvProc)

{

assert(m_pSrvProc!=NULL);

}

/****************************************************************************

*Class:

XPLib

*Function:

~XPLib(void)

*Purpose:

destroy the class

*Parameter:

*Return:

****************************************************************************/

XPLib::~XPLib(void)

{

}

/****************************************************************************

*Class:

XPLib

*Function:

void printError (SRV_PROC *pSrvProc, CHAR* szErrorMsg)

*Purpose:

send error message to Client

*Parameter:

[in]errroMsg

error message

*Return:

****************************************************************************/

void XPLib::printError (const char * errroMsg)

{

srv_sendmsg(m_pSrvProc, SRV_MSG_ERROR, XP_SEND_TO_IBMS_ERROR, SRV_INFO, 1,

NULL, 0, (DBUSMALLINT) __LINE__,

const_cast<char *>(errroMsg),

SRV_NULLTERM);

srv_senddone(m_pSrvProc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);

}

/****************************************************************************

*Class:

XPLib

*Function:

void printUsage (SRV_PROC *pSrvProc)

*Purpose:

send XP usage info to client

*Parameter:

[in]usage

usage of the extended procedure

*Return:

****************************************************************************/

void XPLib::printUsage (const char * usage)

{

srv_sendmsg(m_pSrvProc, SRV_MSG_ERROR, XP_SEND_TO_IBMS_ERROR, SRV_INFO, 1,

NULL, 0, (DBUSMALLINT) __LINE__,

const_cast<char *>(usage),

SRV_NULLTERM);

srv_senddone(m_pSrvProc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);

}

/****************************************************************************

*Class:

XPLib

*Function:

void XPLib::readIntPara(int nParaIndex, int & iValue,bool bAllowNull)

*Purpose:

Read int type parameter

*Parameter:

[in]paraIndex

the Index of the parameter

[out]value

value of the parameter

[in]bAllowNull

wether the parameter is allowed null

Default:true

[in]string paraName

Default:empty string

*Return:

none

****************************************************************************/

void XPLib::readIntPara( int nParaIndex, int &iValue, bool bAllowNull , string paraName/*=""*/)

{

if( paraName.empty())

{

paraName = "参数" + nParaIndex;

}

if( nParaIndex < 1 )

{

THROW_EX( paraName + "的参数应该是从1开始的整数" );

}

// Make sure parameter is a INPUT parameter

if (srv_paramstatus(m_pSrvProc, nParaIndex) == -1)

{

THROW_EX( paraName + "应该是一个输入类型的参数" );

}

BOOL bfNULL; // Whether parameter is null

BYTE pbType[100]={0}; // Type of Parameter

ULONG ulMaxLen = 256; // Max Length of Parameter

ULONG ulActualLen; // Actual Length of Paramter

if ( srv_paraminfo(m_pSrvProc, nParaIndex, pbType, &ulMaxLen, &ulActualLen, NULL, &bfNULL) != SUCCEED )

{

THROW_EX( "读取" + paraName +"失败" );

}

//Check datatype

if(pbType[0] != 0x26) //INT Type

{

stringstream ss;

ss << paraName <<" 不是一个int的数据类型。类型值为:"

<< static_cast<int>(pbType[0])<<endl;

THROW_EX(ss.str());

}

//Check data length must =< 9

if( ulActualLen > 9 )

{

stringstream ss;

ss << paraName <<" 数据类型太长了,对于一个整形数据来说。";

THROW_EX(ss.str());

}

//Read value

if(srv_paraminfo( m_pSrvProc, nParaIndex, pbType, &ulMaxLen, &ulActualLen,

reinterpret_cast<BYTE *>( &iValue ), &bfNULL) != SUCCEED )

{

stringstream ss;

ss << paraName << "读取数据值失败。";

THROW_EX(ss.str());

}

}

/****************************************************************************

*Class:

XPLib

*Function:

void XPLib::readVarcharPara(int nParaIndex, CHAR* szValue, bool bAllowNull)

*Purpose:

Read Varchar type parameter

*Parameter:

[in]paraIndex

the Index of the parameter

[out]szValue

value of the parameter

[in]nStringLength

maxLength of the value string

Default:-1

if it's -1, not check it

[in]bAllowNull

wether the parameter is allowed null

Default:true

[in]paraName

name of parameter

Default:empty string

*Return:

****************************************************************************/

void XPLib::readVarcharPara(int nParaIndex, char * szValue, int nMaxLength /*=-1*/ ,

bool bAllowNull /*= true*/,string paraName/*=""*/)

{

if(paraName.empty())

{

paraName = "参数" + nParaIndex;

}

assert( nParaIndex > 0 );

// Make sure parameter is a INPUT parameter

if (srv_paramstatus(m_pSrvProc, nParaIndex) == -1)

{

THROW_EX( paraName + "应该是一个输入类型的参数" );

}

BOOL bfNULL; // Whether parameter is null

BYTE pbType[100]={0}; // Type of Parameter

ULONG ulMaxLen = 256; // Max Length of Parameter

ULONG ulActualLen; // Actual Length of Paramter

if ( srv_paraminfo(m_pSrvProc, nParaIndex, pbType, &ulMaxLen, &ulActualLen, NULL, &bfNULL) != SUCCEED )

{

THROW_EX( "读取" + paraName +"失败" );

}

//check Length

if( ulActualLen > (UINT) nMaxLength )

{

stringstream ss;

ss << paraName << "长度超过该字段允许的最大长度" << nMaxLength ;

THROW_EX(ss.str());

}

//Check null allowed

if( !bAllowNull && bfNULL)

{

stringstream ss;

ss << paraName << "不能为空" ;

THROW_EX(ss.str());

}

//Check datatype

if( pbType[0] != 167 ) //Varchar data type

{

stringstream ss;

ss << paraName << "不是varchar数据类型。类型值为:"

<< static_cast<int>(pbType[0])<<endl;

THROW_EX(ss.str());

}

//Read value

if(srv_paraminfo( m_pSrvProc, nParaIndex, pbType, &ulMaxLen, &ulActualLen,

reinterpret_cast<BYTE *>(szValue), &bfNULL) != SUCCEED )

{

stringstream ss;

ss << "读取" << paraName <<"失败";

THROW_EX(ss.str());

}

}

}

}

5.生成UDP报文,发送到应用程序

这里的任务就很简单了,读取配置文件的信息"C:\\XPdoorwebsite.config.ini",然后根

据配置文件中的IP地址和端口发送应用程序,注意在写UDP通讯程序时,不要再加入Sock

et API DLL,因为SQL Server中已经引用了该DLL,更不要释放该DLL,那样会导致SQL

Server的数据库停止.而且要特别的小心,不能发生程序错误,因为扩展存储过程在SQ

L Server的进程空间执行,所以要捕获所有异常.就像我们做的

catch(...)

{

lib.printError("Unkowned exception occured");

return XP_ERROR;

}

下面给出UDP初始化和发送数据的代码:

void RowInsertEvent::CreateUdpSocket()

{

if(( m_socket = socket(AF_INET, SOCK_DGRAM, 0)) == SOCKET_ERROR)

{

THROW_BY_ERROR_CODE(GetLastError())

m_socket=NULL;

}

}

void RowInsertEvent::UnicastSend(const char *text, const char *ip, int port)

{

struct hostent *host;

SOCKADDR_IN addr;

int bytesSent = 0;

if( m_socket == NULL)

{

CreateUdpSocket();

}

addr.sin_family = AF_INET;

addr.sin_port = htons((unsigned short) port);

addr.sin_addr.s_addr = inet_addr(ip);

if(addr.sin_addr.s_addr == INADDR_NONE)

{

host = NULL;

host = gethostbyname(ip);

if(host == NULL)

{

THROW_BY_ERROR_CODE( WSAGetLastError() );

}

memcpy(&addr.sin_addr, host->h_addr_list[0], host->h_length);

}

if((bytesSent = sendto( m_socket, text, static_cast<int>(strlen(text)), 0, (struct sockaddr*) &addr,

sizeof(addr))) == SOCKET_ERROR)

{

THROW_BY_ERROR_CODE( WSAGetLastError() );

}

}

6.当这一切都完成了, 就是编译该工程,发布的时候,一定要选择使用在静态库中使用MFC

然后复制该动态连接库 XPDoorWebSite.dll 复制到 SQL Server 的\Binn 目录中或者System32目录下

从 Visual Studio 数据项目添加新的扩展存储过程,

方法是使用 SQL Server 企业管理器,或者执行如下

use master

go

SQL 命令:

sp_addextendedproc 'xp_door_web_site', 'XPDoorWebSite.dll'

7.测试该存储过程:

use master

go

xp_door_web_site 'dyj','http://dyj.com.cn','Hello',1

记住一定用单引号,不能使用双引号

如果一些正常,提示出'命令成功完成'

8.构建配置文件,在本例子中配置文件为C:\\XPdoorwebsite.config.ini,里面包含了通

讯的配置一个简单的示例:

[Client]

IP=192.9.207.145

Port=10006

9.在其他的事务性质的数据库中创建触发器,在触发器中,使用该存储过程.

在本例中:

在lkgov的数据库上运行SQL语句:

use lkgov

go

IF EXISTS (SELECT name FROM sysobjects

WHERE name = 'msg_insert_trigger' AND type = 'TR')

DROP TRIGGER msg_insert_trigger

go

create trigger msg_insert_trigger on msg

for insert

as

begin

declare @type int

declare @userid varchar(12)

declare @msg varchar(500)

declare @url varchar(500)

select

@userid = userid,

@msg= msg,

@url =url,

@type = type

from inserted

exec master.dbo.xp_door_web_site @userid,@url,@msg,@type

end

这样每次在表msg中插入数据的时汗存储过程都会调用xp_door_web_site

总的说来过程是比较繁琐,但也没有什么高深的东西,熟练掌握后,就能很容易的使用了.

期待在SQL Server 2005使用C# 等托管代码写扩展的存储过程..

^0^ Good Luck

2005-6-20

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有