前两天看了一些关于数据库同步的文章,拿出来给那些需要学习的人共享
利用数据库复制技术 实现数据同步更新
复制的概念
复制是将一组数据从一个数据源拷贝到多个数据源的技术,是将一份数据发布到多个存储站点上的有效方式。使用复制技术,用户可以将一份数据发布到多台服务器上,从而使不同的服务器用户都可以在权限的许可的范围内共享这份数据。复制技术可以确保分布在不同地点的数据自动同步更新,从而保证数据的一致性。
SQL复制的基本元素包括
出版服务器、订阅服务器、分发服务器、出版物、文章
SQL复制的工作原理
SQL SERVER 主要采用出版物、订阅的方式来处理复制。源数据所在的服务器是出版服务器,负责发表数据。出版服务器把要发表的数据的所有改变情况的拷贝复制到分发服务器,分发服务器包含有一个分发数据库,可接收数据的所有改变,并保存这些改变,再把这些改变分发给订阅服务器
SQL SERVER复制技术类型
SQL SERVER提供了三种复制技术,分别是:
1、快照复制(呆会我们就使用这个)
2、事务复制
3、合并复制
只要把上面这些概念弄清楚了那么对复制也就有了一定的理解。接下来我们就一步一步来实现复制的步骤。
第一先来配置出版服务器
(1)选中指定[服务器]节点
(2)从[工具]下拉菜单的[复制]子菜单中选择[发布、订阅服务器和分发]命令
(3)系统弹出一个对话框点[下一步]然后看着提示一直操作到完成。
(4)当完成了出版服务器的设置以后系统会为该服务器的树形结构中添加一个复制监视器。同时也生成一个分发数据库(distribution)
第二创建出版物
(1)选中指定的服务器
(2)从[工具]菜单的[复制]子菜单中选择[创建和管理发布]命令。此时系统会弹出一个对话框
(3)选择要创建出版物的数据库,然后单击[创建发布]
(4)在[创建发布向导]的提示对话框中单击[下一步]系统就会弹出一个对话框。对话框上的内容是复制的三个类型。我们现在选第一个也就是默认的快照发布(其他两个大家可以去看看帮助)
(5)单击[下一步]系统要求指定可以订阅该发布的数据库服务器类型,SQLSERVER允许在不同的数据库如 ORACLE或ACCESS之间进行数据复制。但是在这里我们选择运行"SQL SERVER 2000"的数据库服务器
(6)单击[下一步]系统就弹出一个定义文章的对话框也就是选择要出版的表
(7)然后[下一步]直到操作完成。当完成出版物的创建后创建出版物的数据库也就变成了一个共享数据库。
第三设计订阅
(1)选中指定的订阅服务器
(2)从[工具]下拉菜单中选择[复制]子菜单的[请求订阅]
(3)按照单击[下一步]操作直到系统会提示检查SQL SERVER代理服务的运行状态,执行复制操作的前提条件是SQL SERVER代理服务必须已经启动。
(4)单击[完成]。完成订阅操作。
完成上面的步骤其实复制也就是成功了。但是如何来知道复制是否成功了呢?这里可以通过这种方法来快速看是否成功。展开出版服务器下面的复制——发布内容——右键发布内容——属性——击活——状态然后点立即运行代理程序接着点代理程序属性击活调度把调度设置为每一天发生,每一分钟,在0:00:00和23:59:59之间。接下来就是判断复制是否成功了打开C:\Program Files\Microsoft SQL Server\MSSQL\REPLDATA\unc\XIAOWANGZI_database_database下面看是不是有一些以时间做为文件名的文件夹差不多一分中就产生一个。要是你还不信的话就打开你的数据库看在订阅的服务器的指定订阅数据库下看是不是看到了你刚才所发布的表—
一个手工同步的方案
--定时同步服务器上的数据
--例子:
--测试环境,SQL Server2000,远程服务器名:xz,用户名为:sa,无密码,测试数据库:test
--服务器上的表(查询分析器连接到服务器上创建)
create table [user](id int primary key,number varchar(4),name varchar(10))
go
--以下在局域网(本机操作)
--本机的表,state说明:null 表示新增记录,1 表示修改过的记录,0 表示无变化的记录
if exists (select * from dbo.sysobjects where id = object_id(N'[user]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [user]
GO
create table [user](id int identity(1,1),number varchar(4),name varchar(10),state bit)
go
--创建触发器,维护state字段的值
create trigger t_state on [user]
after update
as
update [user] set state=1
from [user] a join inserted b on a.id=b.id
where a.state is not null
go
--为了方便同步处理,创建链接服务器到要同步的服务器
--这里的远程服务器名为:xz,用户名为:sa,无密码
if exists(select 1 from master..sysservers where srvname='srv_lnk')
exec sp_dropserver 'srv_lnk','droplogins'
go
exec sp_addlinkedserver 'srv_lnk','','SQLOLEDB','xz'
exec sp_addlinkedsrvlogin 'srv_lnk','false',null,'sa'
go
--创建同步处理的存储过程
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[p_synchro]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[p_synchro]
GO
create proc p_synchro
as
--set XACT_ABORT on
--启动远程服务器的MSDTC服务
--exec master..xp_cmdshell 'isql /S"xz" /U"sa" /P"" /q"exec master..xp_cmdshell ''net start msdtc'',no_output"',no_output
--启动本机的MSDTC服务
--exec master..xp_cmdshell 'net start msdtc',no_output
--进行分布事务处理,如果表用标识列做主键,用下面的方法
--BEGIN DISTRIBUTED TRANSACTION
--同步删除的数据
delete from srv_lnk.test.dbo.[user]
where id not in(select id from [user])
--同步新增的数据
insert into srv_lnk.test.dbo.[user]
select id,number,name from [user] where state is null
--同步修改的数据
update srv_lnk.test.dbo.[user] set
number=b.number,name=b.name
from srv_lnk.test.dbo.[user] a
join [user] b on a.id=b.id
where b.state=1
--同步后更新本机的标志
update [user] set state=0 where isnull(state,1)=1
--COMMIT TRAN
go
--创建作业,定时执行数据同步的存储过程
if exists(SELECT 1 from msdb..sysjobs where name='数据处理')
EXECUTE msdb.dbo.sp_delete_job @job_name='数据处理'
exec msdb..sp_add_job @job_name='数据处理'
--创建作业步骤
declare @sql varchar(800),@dbname varchar(250)
select @sql='exec p_synchro' --数据处理的命令
,@dbname=db_name() --执行数据处理的数据库名
exec msdb..sp_add_jobstep @job_name='数据处理',
@step_name = '数据同步',
@subsystem = 'TSQL',
@database_name=@dbname,
@command = @sql,
@retry_attempts = 5, --重试次数
@retry_interval = 5 --重试间隔
--创建调度
EXEC msdb..sp_add_jobschedule @job_name = '数据处理',
@name = '时间安排',
@freq_type = 4, --每天
@freq_interval = 1, --每天执行一次
@active_start_time = 00000 --0点执行
go
帧中继环境下Ping的实现
一. ICMP协议和Ping 命令
为了让互联网中的路由器报告错误或提供有关意外情况的信息,TCP/IP中加入了一个特殊用途的报文机制。这个机制叫做Internet控制报文协议ICMP(Internet Control Message Protocol),它是IP的一部分,并在每个IP实现中必须有它。最初的设计虽然是为了允许路由器向主机报告投递出错的原因,但是ICMP并没有限制仅在路由器上使用。尽管限制某些ICMP报文的使用,但是任一台机器可以向任何其他机器发送ICMP报文。因此,主机可以使用ICMP与路由器或另一台主机通信。
当一台主机创建了一个I P 数据报时,主机会产生一个覆盖整个头部的校验和。无论何时收到一个数据报,校验和都被用于验证头部是否无损地到达。为了验证校验和,计算机对接收到的数据报的整个头部(包括校验和域)重新计算校验和。如果I P 头部中的某一位在传输过程中被破坏,则计算出的校验和不为零。当改变头部中的域时(例如将生存时间TIME TO LIVE 域递减),路由器必须在转发该数据报前,重新计算校验和。
发现校验和错时的处理非常简单:数据报必须立即丢弃,而不作进一步的处理。接收者不能相信数据包头部中的任何域,因为接收者不知道哪一位被改变了。甚至也不能发一个出错消息给发送者,因为接收者不能相信包头中的源地址。同样,接收者也不能转发被损坏的数据包,因为它也不能相信包头中的目的地址。因此,接收者除了将被损坏的数据包丢弃外别无选择。
ICMP报文是放在一个IP数据报的数据部分中通过互联网的。ICMP报文的最终目的不是一个应用程序或是目的机器上的用户,而是该机上处理它的Internet协议软件模块。总之,ICMP在两台机器上的Internet协议软件之间提供了通信。
ICMP中的回送请求(echo request)和回送应答(echo reply)报文为网络管理者或用户提供了一个识别网络问题的调试工具。主机或路由器向指定目的站发送ICMP回送请求报文,任何收到回送请求的机器形成回送应答并把它返回最初的发送者。回送请求包含一个可选数据区;应答包含了在请求中所发送数据的一个拷贝。回送请求及其关联的应答可用来测试目的站是否可达和是否响应。因为请求和应答都是在IP数据报中传送的,所以应答的成功接收就证实传送系统的主要部分是正常的。
在许多系统上,用户所使用的发送ICMP回送请求的命令叫做Ping。较复杂的Ping的发送一系列ICMP回送请求,捕获响应并提供丢失数据报的统计。不太复杂的版本仅仅发送一个ICMP回送请求并等待应答。
二. IP和ICMP报文格式
ICMP回送请求和回送应答报文格式如下:
IP报文格式如下:
程序中利用函数CheckSum()来计算和验证IP的首部校验和以及ICMP的校验和。将要计算校验和的数据部分看作为一个16位的整数序列,并定义校验和是数据中所有16位整数的和的二进制反码。在函数CheckSum()中使用了32位(长)算法来累加得到一个和,然后通过向这个整数和中增加所有进位位的方法,将结果折合成一个16位的值。最后,CheckSum()返回结果的反码。注意,每次调用CheckSum()计算校验和之前,要先将校验和域清空,再计算赋值。
三. 程序思路
该实验的实现是在只是前面提到的ARP/InARP的基础上增加了对Ping的处理,仍然采用多线程结构,实现互斥操作。
在主线程中,增加了用户可以使用Ping命令,并做出相应处理。写线程仍然是从数据包队列中取出一个数据包,并在写事件到来时将其发送出去。 读线程在读取一个数据包后判断其包类型时,增加了对ICMP回送请求和回送应答的处理。
四. 主要的数据结构
Ping的实现是ARP/InARP实现的基础上增加了以下一些数据结构:
struct S_PING_PKT
////////////////////////////////////////////////////////////////////////////////
// 定义了Ping报文的格式,包括帧中继报文头,
// IP数据报头,ICMP报文格式;
/////////////////////////////////////////////////////////////////////////////////
{
//FrameRelay head
unsigned char frameHead[2];
// IP head
unsigned char ip_ver; // 0x45
unsigned char ip_tos; // 0
unsigned short ip_tl; // 0x0020
unsigned short ip_id;
unsigned short ip_ffo; // 0x0000
unsigned char ip_ttl; // 32
unsigned char ip_pro; // 1
unsigned short ip_chk;
unsigned char ip_sip[4];
unsigned char ip_tip[4];
// ICMP head
unsigned char type; // 8=echo request 0=Echo reply
unsigned char code; // 0
unsigned short chk;
unsigned short id;
unsigned short seq;
//可选数据
unsigned char data[32];
};
struct S_pingState; //用于记录发送出去的Ping报文的状态信息,包括Ping的目的IP地址,该报文是否已发送,是否已经接收到应答;
六. 程序结构与主要流程
(1) 主线程: 接收用户输入,在用户使用Ping命令时,将用户输入的目的IP地址存放到S_pingState结构中去,调用函数SendPing()
函数SendPing()实现如下:
(2) 读线程
函数handlePingRequest():
生成一个相应的回送请求,并暂存到待发送数据队列中
函数handlePingReply():
根据收到的应答的序列号,将相应的S_pingState中的已接收标志置为1
七.程序中其他一些处理
1. 并发控制:由于本程序是一个多线程的程序,多个线程运行时是共享代码和数据的。如果不对这种共享作一些控制的话肯定会出现不可预料的结果。本程序对并发的处理采用 MFC 中的并发锁机制,由于线程会共享的数据被封装在两个类里面(C_QUEUE_INFO和C_DLCI_INFO),线程操作数据都是通过这两个类的实例进行的,所以只要在两个类的代码里实现并发锁就可以了。
2. 需要特别注意的是数据链路帧的帧头不是固定的,有的情况下(发送配置类原语)是8个字节,真正发送数据的时候是2个字节。
3. 协议中对数字的处理可能会和大家最初设想的很不一样。譬如主机的IP地址,大家很可能会认为是和标准Socket里面那样是一个以网络字节序存储的结构体来表示的,结果经过分析发现不是,而是用了四个字节,每个字节存储相应的点分十进制数字。如 100.1.1.1 在协议数据包中是这样构成的:[100] [1] [1] [1],其中[ ]表示一个字节。
4. 校验和的处理也很奇怪。传输时也不是采用网络字节序,而是采用了位串的方式。
参考资料:
RFC791, J. Postel. Internet Protocol, Sep-01-1981.
RFC826, D.C. Plummer. Ethernet Address Resolution Protocol, Nov-01-1982.
RFC1293, T. Bradley, C. Brown, Inverse Address Resolution Protocol. January 1992.
RFC1294, T. Bradley, C.Brown, A. Malis. Multiprotocol Interconnect over Frame Relay. January 1992.
CCITT X.36
附录:
A. 帧中继的一些原语
#define DL_DATA_IND 4
#define DL_DATA_REQ 4
/*
ID (04)
DSAP
USER_DATA
*/
#define NUM_DL_PARA 12
#define DL_PARA_REQ 7
#define DL_PARA_IND 7
/*
ID (07)
DSAP
..........
**********
PARA
VALUE_LOW
VALUE_HIGH
**********
..........
*/
/* ------------PARA TABLE--------------
PAR 1: CONFIG
BIT 0: 1-ON,0-OFF
BIT 1: 1-LMI:Q.933 Annex A, 0-LMI:NONE
PAR 2: FIRST_DLCI
PAR 3: NUM_DLCI
PAR 4: NUM_PVC
PAR 5: N203
PAR 6: N391
PAR 7: N392
PAR 8: N393
PAR 9: T391
PAR 10: T392
PAR 11: BAUD RATE
0---XXX
1---XXX
2---XXX
3---75
4---150
5---300
6---600
7---1200
8---2400
9---4800
10---9600
11---19200
12---48000
13---64000
14---128000
15---256000
PAR 12:CLOCK 1-INTER; 0-EXTERN */
CONFIG: 3--使用Q.933协议中附件A的链路管理协议 (缺省值)
1--不使用上述链路管理协议
DLCI NUM: 永久虚电路的个数
FIRST DLCI: 第一条永久虚电路的DLCI编号,缺省为16
N391: 完全状态(所有PVC状态)轮询计数器
N392: 差错/恢复计数器(N392应小于或等于N393)。
N393: 事件监测计数器(如果N393置为一个比N391小很多的值, 则链路可能在差错和无差错状态之间切换而未能被DTE或网络觉察)。
T391: 链路完整性校验轮询计时器
T392: 轮询校验计时器
#define DL_WIND_IND 8
/*
ID (08)
DSAP
WIND_SIZE
*/
#define DL_PERMIT_REQ 9
/*
ID (09)
DSAP
PERMIT_SIZE
*/
#define DL_STATE_IND 10
/*
ID (10)
DSAP (0)
STATE
**********
DSAP
DLCI_LOW
DLCI_HIGH
STATE : 0---NOT READY 1---READY
**********
*/
B. 物理层原语
VL3-X25规程控制卡向高层提供了按X.211建议设计的网络服务原语。当VL3-X25规程控制卡设置成物理模式时可以使用这些原语。VL3规程控制卡原语用VL3规程控制卡驱动程序作为数据块发送和接收。用户把每个原语看成一个数据块,交给VL3规程控制卡驱动程序,VL3规程控制卡控制卡驱动程序也把原语以数据块的形式交给用户。
PH_ACT_REQ激活请求、PH_ACT_IND激活指示
格式
ID (01)
说明
PH_ACT_REQ用于激活物理链路,PH_ACT_IND用于指示物理链路已激活。
PH_DEACT_REQ撤消请求、PH_DEACT_IND撤消指示
格式
ID (02)
说明
PH_DEACT_REQ用于撤消已激活的物理链路、PH_DEACT_IND用于指示物理链路已撤消。
PH_DATA_REQ数据请求、PH_DATA_IND数据指示
格式
ID (03)
USER_DATA
说明
PH_DATA_REQ用于发数据,PH_DATA_IND用于指示收到的数据。
PH_PARA_REQ参数请求、PH_PARA_IND参数指示
格式
ID (04)
.....................
*********************
PARA
VALUE_LOW
VALUE_HIGH
*********************
.....................
说明
PH_PARA_REQ用于设置或读出参数。当原语没有参数字段时从VL3规程控制卡中读出参数。否则设置新的参数值。参数的编号和意义如下表。
以下的代码是可以自己测试!如果大家有兴趣可以直接去看看
<% @ Import Namespace="System" %>
<% @ Import Namespace="System.Data" %>
<% @ Import Namespace="System.Data.OleDb" %>
<% @ Import Namespace="System.Text" %>
<% @ Import Namespace="System.IO" %>
<script language="C#" runat=server>
public class Control_year
{
public Control_year()
{
}
public int judge_if_year = 0;
public int www;
public string[,] org_data(string Thisyears, string Thismonths)
{
int i =1;
int j =0;
int k =0;
int num_day = 0;
int mydata =1;
string[] num_dig = new string[2];
//num_dig = change_type(Thisyears,Thismonths);
//转换成数字型
int Thisyear =Convert.ToInt32(Thisyears);
int Thismonth = Convert.ToInt32(Thismonths);
int [,] return_data = new int[6,7]; //返回的是个六行七列的二维数组
int [] return_month_day = new int[13];
string [,] return_string =new string[6,7];
if ((Thisyear%4 == 0 && Thisyear%100 != 0) || Thisyear%400 == 0)
{
judge_if_year = 1;
}
else
{
judge_if_year = 0;
}
//一年有12个月
return_month_day[0] = 0;
return_month_day[1] = 0;
if (judge_if_year == 0)
{
return_month_day[2] =2;
}
else
{
return_month_day[2] =3;
}
return_month_day[3] = 0;
return_month_day[4] = 1;
return_month_day[5] = 0;
return_month_day[6] = 1;
return_month_day[7] = 0;
return_month_day[8] = 0;
return_month_day[9] = 1;
return_month_day[10]= 0;
return_month_day[11]= 1;
return_month_day[12]= 0;
num_day = count_day_index(Thisyear,Thismonth,1,judge_if_year);
for(j=num_day;j<7;j++)
{
return_data[0,j] = mydata++;
// mydata ++;
}
//以上这个循环是对1号到7号之间进行判断
while (mydata <32 - return_month_day[Thismonth])
{
return_data[i,k] = mydata++;
//mydata ++;
k++;
k = k%7;
if (k == 0)
{
i++;
}
}
for(i=0;i<6;i++)
{
for(j=0;j<7;j++)
{
if (return_data[i,j] !=0)
{
return_string[i,j] = Convert.ToString(return_data[i,j]);
}
else
{
return_string[i,j] = "";
}
}
}
return return_string;
//计算某天是星期几
}
//计算某天是星期几的函数
public int count_day_index(int strYear,int strMonth,int strDate,int judge_if_year)
{
int this_year = strYear ;
int this_month = strMonth;
int this_date = strDate;
int[] diff_month_day = new int[13];
diff_month_day[0] = 0;
diff_month_day[1] = 0;
if (judge_if_year == 1)
{
diff_month_day[2] =-2;
}
else
{
diff_month_day[2] =-3;
}
diff_month_day[3] = 0;
diff_month_day[4] = -1;
diff_month_day[5] = 0;
diff_month_day[6] = -1;
diff_month_day[7] = 0;
diff_month_day[8] = 0;
diff_month_day[9] = -1;
diff_month_day[10]= 0;
diff_month_day[11]= -1;
diff_month_day[12]= 0;
//现在我们从1990 年开始计算
int Total_year = 0;
int[] flag = new int[100];
int i = 0;
int j = 0;
int num =0;
int Total_month =0;
int Total_day = 0;
for (i=1990; i <this_year; i++)
{
if ((i%4 == 0 && i%100 != 0) || i%400 == 0)
{
//flag[num] = i;
num = num +1;
}
}
Total_year = (this_year - 1990)*365 + num ; //计算计算前面几年的总天数
Total_month = (this_month-1)*31; //计算本年的该月之前的的总天数
for(j=0; j<this_month ;j++)
{
Total_month = Total_month + diff_month_day[j];
}
Total_day = Total_year + Total_month + this_date;
//www = Total_day;
return Total_day%7 ;
}
public string[] change_type(string Thisyears, string Thismonths)
{
int this_years = 0;
int this_months = 0;
string [] dig_num = new string[2];
if (Thisyears !="" && Thismonths !="")
{
try
{
this_years = Convert.ToInt32(Thisyears);
this_months = Convert.ToInt32(Thismonths);
}
catch(Exception ee)
{
if (ee.ToString().Length !=0)
{
dig_num[0] = "输入的类型不正确,请重新输入";
dig_num[1] = "谢谢合作";
return dig_num;
}
}
if (this_years <1990&& this_months >12)
{
dig_num[0] = "请输入大于1990的年份";
dig_num[1] = "谢谢合作";
return dig_num;
}
else
{
dig_num[0] = Convert.ToString(this_years);
dig_num[1] = Convert.ToString(this_months);
}
}
return dig_num;
}
}
</script>
<% @ Page Language="C#" Debug="true" %>
<%
Control_year tester = new Control_year();
string year = "2002";
string month = "9";
string [,] endding = tester.org_data( year, month );
Response.Write("<table border = 1>");
for( int i = 0; i < endding.GetLength( 0 ); i ++ )
{
Response.Write("<tr>");
for( int j = 0; j < endding.GetLength( 1 ); j ++ )
{
Response.Write("<td>");
Response.Write(endding[i,j].ToString());
Response.Write("</td>");
}
Response.Write("</tr>");
}
Response.Write("</table>");
%>
WH_CALLWNDPROC 发送到窗口的消息。由SendMessage触发
WH_CALLWNDPROCRET 发送到窗口的消息。由SendMessage处理完成返回时触发
WH_GETMESSAGE 发送到窗口的消息。GetMessage或PeekMessage触发
WH_KEYBROAD 键盘钩子,键盘触发消息。WM_KEYUP或WM_KEYDOWN消息
WH_KEYBROAD_LL 地层键盘钩子
WH_MOUSE 鼠标钩子,查询鼠标事件消息
WH_MOUSE_LL 低层键盘钩子
WH_HARDWARE 非鼠标、键盘消息时
WH_MSGFILTER 对话框、菜单或滚动条要处理一个消息时。该钩子是局部的。
WH_SYSMSGFILTER 同WH_MSGFILTER一样,系统范围的。
WH_DEBUG 调试钩子,用来给钩子函数除错
WH_JOURNALRECORD 监视和记录输入事件
WH_JOURNALPLAYBACK 回放用WH_JOURNALRECORD记录事件
WH_SHELL 外壳钩子,当关于WINDOWS外壳事件发生时触发.
WH_CBT 当基于计算机的训练(CBT)事件发生时
WH_FOREGROUNDIDLE 前台应用程序线程变成空闲时候,钩子激活。
二、钩子的类型:
全局钩子:全局钩子可以挂钩其他进程的事件,有两种:基于线程的,它将捕获其它进程中某一特定线程的事件。简言之,就是可以用来观察其它进程中的某一特定线程将发生的事件。2,系统范围的,将捕捉系统中所有进程将发生的事件消息。
局部钩子:仅钩挂您自己进程的事件。
三、安装钩子:
SetWindowsHookEx
函数原形:HHOOK SetWindowsHookEx(
int idHook, // 钩子类型,见[一]
HOOKPROC lpfn, // 钩子函数地址
INSTANCE hMod, // 钩子所在的实例的句柄,
DWORD dwThreadId // 钩子所监视的线程的线程号
)
hMod: 对于线程序钩子,参数传NULL;对于系统钩子:参数为钩子DLL的句柄
dwThreadId:对于全局钩子,该参数为NULL。
返回:成功:返回SetWindowsHookEx返回所安装的钩子句柄;
失败:NULL;
四、卸载钩子:
UnhookWindowsHookEx
函数原形:BOOL UnhookWindowsHookEx(
HHOOK hhk // 要卸载的钩子句柄。
)
五、钩子函数:
MyHookProc
钩子函数是回调函数。当安装的钩子被钩到指定的事件消息后,系统会自动调用钩子函数进行处理。
定义如下:
LRESULT WINAPI MyHookProc(
int nCode , // 指定是否需要处理该消息
WPARAM wParam, // 包含该消息的附加消息
LPARAM lParam // 包含该消息的附加消息
)
六、调用下一个钩子
CallNextHookEx
既然WINDOWS的钩子结构都保存在一个链表里边,很明显,消息将会被一个个往下传递,最后到达目标窗口,所以,我们处理了以后,由责任将消息传递给下一个钩子。当然你也可以不,但我还是建议您继续传递下去。
函数定义如下:
LRESULT CallNextHookEx(
HHOOK hhk, // 是您自己的钩子函数的句柄。用该句柄可以遍历钩子链
int nCode, // 把传入的参数简单传给CallNextHookEx即可
WPARAM wParam, // 把传入的参数简单传给CallNextHookEx即可
LPARAM lParam // 把传入的参数简单传给CallNextHookEx即可
)
<html>
<head>
<script language = "javascript">
function enterkey()
{
try
{
var elkeydown = window.event.scrElement;
if (event.keycode == 13 && el_keydown.type == "text")
//表示来自文本框的回车
{
bt_ok2.focus();
}
}
catch(e)
{
return;
}
}
</script>
</head>
</html>
下面是KEYCODE的键盘码对应表
左方向键: 37
右方向键: 39
上方向键: 38
下方向键: 40
回车键: 13
空格键: 32
BACKSPACE键:8
CTRL键: 17
SHIFT键: 16
TAB键: 9
如果要深入了解网页上的事件扑获查看:window.event的相关资料