网络上传输的消息经常是经过加密和压缩,有的特定类型的消息可能还需要进行其它变形,ESFramework通过INetMessageHook对这些功能提供支持。需要说明的是,ESFramework对消息进行截获(Hook)处理有两种方式,一是仅仅Hook处理消息主体(Body),而不对消息头作任何变换;另一种方式是对整个消息(包括消息头和主体)都进行Hook处理。通常,第一种方式已经能够满足我们的大多数应用,并且效率也更高,如果应用有更特殊的要求,可以采用第二种方式。本文先介绍第一种方式,后面的文章中会对第二种给予讲解。
INetMessageHook定义如下:
1 public interface INetMessageHook
2 {
3 //转换消息
4 NetMessage CaptureRecievedMsg(NetMessage msg) ;//在交给处理器之前
5 NetMessage CaptureBeforeSendMsg(NetMessage msg) ;//在发送出去之前
6
7 bool Enabled{get ;set ;} //是否启用本Hook
8 }
通过INetMessageHook接口可以看到,当消息进入系统时回被CaptureRecievedMsg方法截获(比如,进行解密处理),当把消息发送到网络之前,将被CaptureBeforeSendMsg方法截获(比如,进行加密处理)。
如果有多个INetMessageHook,则可以形成一个HookList,消息经过HookList时,分别按顺序被每个INetMessageHook处理,ESFramework中的HookList的参考实现是EsbNetMessageHook,并且它也实现了INetMessageHook接口,这样就可以把一个Hook链当作一个Hook了。
其源码如下:
EsbNetMessageHook
1 public class EsbNetMessageHook : INetMessageHook
2 {
3 #region property
4 private IList hookList = new ArrayList() ;
5 public IList HookList
6 {
7 set
8 {
9 this.hookList = value ;
10 }
11 }
12
13 #region Enabled
14 private bool enabled = true ;
15 public bool Enabled
16 {
17 get
18 {
19 return this.enabled ;
20 }
21 set
22 {
23 this.enabled = value ;
24 }
25 }
26 #endregion
27
28 #endregion
29
30 #region INetMessageHook 成员
31 public NetMessage CaptureRecievedMsg(NetMessage msg)
32 {
33 if(! this.enabled)
34 {
35 return msg ;
36 }
37
38 if(msg == null)
39 {
40 return null ;
41 }
42
43 NetMessage result = msg ;
44 for(int i=0 ;i<this.hookList.Count ;i++)
45 {
46 INetMessageHook hook = (INetMessageHook)this.hookList[i] ;
47 result = hook.CaptureRecievedMsg(result) ;
48 }
49
50 return result ;
51 }
52
53 public NetMessage CaptureBeforeSendMsg(NetMessage msg)
54 {
55 if(! this.enabled)
56 {
57 return msg ;
58 }
59
60 if(msg == null)
61 {
62 return null ;
63 }
64
65 NetMessage result = msg ;
66 for(int i=this.hookList.Count-1 ;i>=0 ;i-- )
67 {
68 INetMessageHook hook = (INetMessageHook)this.hookList[i] ;
69 result = hook.CaptureBeforeSendMsg(result) ;
70 }
71
72 return result ;
73 }
74
75 #endregion
76 }
需要特别注意的是,HookList的实现中,逐个调用Hook的CaptureRecievedMsg方法的顺序必须与逐个调用CaptureBeforeSendMsg方法的顺序相反,这就是为什么EsbNetMessageHook实现CaptureBeforeSendMsg方法时,出现下列代码的原因:
for(int i=this.hookList.Count-1 ;i>=0 ;i-- )
为了说明为什么需要颠倒Hook链的情况,可以举个例子,假设有一个“原始消息”从系统1发送到系统2,其间经过了两个Hook,一个加密Hook,一个是压缩Hook,则可表示如下:
系统1=》原始消息=》加密=》压缩=》发送
系统2=》接收消息=》解压缩=》解密=》原始消息
还要提醒的是,在具体的Hook实现中,截获处理经常改变Body的大小,如果Body的大小真的发生了变化,一定要更新消息头(IMessageHeader)中的MessageBodyLength字段。
通常,采用Hook时,服务端与客户端是对称的,所以你可以把所有的Hook实现放在一个Dll中,这样服务端和客户端可以共同使用这个Hook Dll了。
由于对网络消息进行压缩是常见的需求,所以我把BaseZipHook纳入到了ESFramework中,并且,这也是IMessageHeader存在ZipMe属性的原因。ZipMe属性用于通知BaseZipHook是否对接收到的本条消息进行解压缩,是否在发送本消息之前进行压缩。
BaseZipHook实现如下:
BaseZipHook
1 public abstract class BaseZipHook :INetMessageHook
2 {
3 public BaseZipHook()
4 {
5 }
6
7 protected abstract byte[] Zip(byte[] data ,int offset ,int size) ;
8 protected abstract byte[] Unzip(byte[] data ,int offset ,int size) ;
9 protected abstract bool IsP2PMessage(NetMessage msg) ;
10
11 #region INetMessageHook 成员
12
13 #region CaptureRecievedMsg
14 public NetMessage CaptureRecievedMsg(NetMessage msg)
15 {
16 if(!this.enabled)
17 {
18 return msg ;
19 }
20
21 if(! msg.Header.ZipMe)
22 {
23 return msg ;
24 }
25
26 if(this.IsP2PMessage(msg))
27 {
28 if(! this.zipP2pMessage)
29 {
30 return msg ;
31 }
32 }
33
34 if(msg.Body == null)
35 {
36 return msg ;
37 }
38
39 msg.Body = this.Unzip(msg.Body ,0 ,msg.Header.MessageBodyLength) ;
40 msg.Header.MessageBodyLength = msg.Body.Length ;
41
42 return msg ;
43 }
44 #endregion
45
46 #region CaptureBeforeSendMsg
47 public NetMessage CaptureBeforeSendMsg(NetMessage msg)
48 {
49 if(!this.enabled)
50 {
51 return msg ;
52 }
53
54 if(! msg.Header.ZipMe)
55 {
56 return msg ;
57 }
58
59 if(this.IsP2PMessage(msg))
60 {
61 if(! this.zipP2pMessage)
62 {
63 return msg ;
64 }
65 }
66
67 if(msg.Body == null)
68 {
69 return msg ;
70 }
71
72 msg.Body = this.Zip(msg.Body ,0 ,msg.Header.MessageBodyLength) ;
73 msg.Header.MessageBodyLength = msg.Body.Length ;
74
75 return msg ;
76 }
77 #endregion
78
79 #region Enabled
80 private bool enabled = true ;
81 public bool Enabled
82 {
83 get
84 {
85 return this.enabled ;
86 }
87 set
88 {
89 this.enabled = value ;
90 }
91 }
92 #endregion
93
94 #region ZipP2pMessage 通常服务端设置为false ,而客户端设置为true
95 private bool zipP2pMessage = true ;
96 public bool ZipP2pMessage
97 {
98 get
99 {
100 return this.zipP2pMessage ;
101 }
102 set
103 {
104 this.zipP2pMessage = value ;
105 }
106 }
107 #endregion
108
109 #endregion
110 }
BaseZipHook是一个抽象类,具体实现的压缩算法由派生类通过覆写Zip和Unzip方法提供。如果要实现一个具体的ZipHook,你可以从BaseZipHook继承,并采用ZipHelper提供的压缩/解压缩功能。
到现在为止,我们已经讨论了关于消息的比较多的内容了,但还有一个非常重要的组件没有讲到,那就是消息分派器ITcpStreamDispatcher,消息分派器直接与Tcp组件或Udp组件联系,并且所有消息的进出都要经过ITcpStreamDispatcher,所以分派器是一个对消息进行Hook的理想位置,并且消息分派器会把具体的消息分派到对应的消息处理器上。欲知道ITcpStreamDispatcher的原理与实现,请继续关注下文:
ESFramework介绍之(5)――消息分派器ITcpStreamDispatcher
上一篇:ESFramework介绍之(3)――消息处理器和处理器工厂
转到 :ESFramework 可复用的通信框架(序)