posts - 5, comments - 0, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2011年11月24日

SQL 不同于与其他编程语言的最明显特征是处理代码的顺序。在大数编程语言中,代码按编码顺序被处理,但是在SQL语言中,第一个被处理的子句是FROM子句,尽管SELECT语句第一个出现,但是几乎总是最后被处理。

      每个步骤都会产生一个虚拟表,该虚拟表被用作下一个步骤的输入。这些虚拟表对调用者(客户端应用程序或者外部查询)不可用。只是最后一步生成的表才会返回 给调用者。如果没有在查询中指定某一子句,将跳过相应的步骤。下面是对应用于SQL server 2000和SQL Server 2005的各个逻辑步骤的简单描述。


复制代码
(8)SELECT (9)DISTINCT  (11)<Top Num> <select list>
(
1)FROM [left_table]
(
3)<join_type> JOIN <right_table>
(
2)ON <join_condition>
(
4)WHERE <where_condition>
(
5)GROUP BY <group_by_list>
(
6)WITH <CUBE | RollUP>
(
7)HAVING <having_condition>
(
10)ORDER BY <order_by_list>
复制代码

逻辑查询处理阶段简介

  1. FROM:对FROM子句中的前两个表执行笛卡尔积(Cartesian product)(交叉联接),生成虚拟表VT1
  2. ON:对VT1应用ON筛选器。只有那些使<join_condition>为真的行才被插入VT2。
  3. OUTER(JOIN):如 果指定了OUTER JOIN(相对于CROSS JOIN 或(INNER JOIN),保留表(preserved table:左外部联接把左表标记为保留表,右外部联接把右表标记为保留表,完全外部联接把两个表都标记为保留表)中未找到匹配的行将作为外部行添加到 VT2,生成VT3.如果FROM子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直到处理完所有的表为止。
  4. WHERE:对VT3应用WHERE筛选器。只有使<where_condition>为true的行才被插入VT4.
  5. GROUP BY:按GROUP BY子句中的列列表对VT4中的行分组,生成VT5.
  6. CUBE|ROLLUP:把超组(Suppergroups)插入VT5,生成VT6.
  7. HAVING:对VT6应用HAVING筛选器。只有使<having_condition>为true的组才会被插入VT7.
  8. SELECT:处理SELECT列表,产生VT8.
  9. DISTINCT:将重复的行从VT8中移除,产生VT9.
  10. ORDER BY:将VT9中的行按ORDER BY 子句中的列列表排序,生成游标(VC10).
  11. TOP:从VC10的开始处选择指定数量或比例的行,生成表VT11,并返回调用者。

注:步骤10,按ORDER BY子句中的列列表排序上步返回的行,返回游标VC10.这一步是第一步也是唯一一步可以使用SELECT列表中的列别名的步骤。这一步不同于其它步骤的 是,它不返回有效的表,而是返回一个游标。SQL是基于集合理论的。集合不会预先对它的行排序,它只是成员的逻辑集合,成员的顺序无关紧要。对表进行排序 的查询可以返回一个对象,包含按特定物理顺序组织的行。ANSI把这种对象称为游标。理解这一步是正确理解SQL的基础。

因为这一步不返回表(而是返回游标),使用了ORDER BY子句的查询不能用作表表达式。表表达式包括:视图、内联表值函数、子查询、派生表和共用表达式。它的结果必须返回给期望得到物理记录的客户端应用程序。例如,下面的派生表查询无效,并产生一个错误:

select * 
from(select orderid,customerid from orders order by orderid)
as d

下面的视图也会产生错误

create view my_view
as
select
*
from orders
order by orderid

      在SQL中,表表达式中不允许使用带有ORDER BY子句的查询,而在T—SQL中却有一个例外(应用TOP选项)。

      所以要记住,不要为表中的行假设任何特定的顺序。换句话说,除非你确定要有序行,否则不要指定ORDER BY 子句。排序是需要成本的,SQL Server需要执行有序索引扫描或使用排序运行符。

posted @ 2012-08-10 15:58 火炎炎 阅读(162) | 评论 (0)编辑 收藏

CMPP2.0开发

一、CMPP协议简介
中国移动通信互联网短信网关接口协议(China Mobile Peer to Peer CMPP),是中国移动梦网内部各SMS参与节点相互交换SMS的官方协议。作为梦网的参与方,移动梦网的增值服务商(Service Provider SP )要按照此协议规范实现SP的部分,才可以将自己的短信通过移动的GSM网络的数据通道传输到最终手机用户上。
实际上,协议规范了3个方面的内容:
。SP与移动的互联网短信网关(Internet Short Message Gateway,ISMG)之间的接口协议
。ISMG之间的接口协议(譬如移动各省、市之间的短信息交换通过ISMG之间进行)
。ISMG与汇接网关(Gateway Name Server GNS,类似互联网上的DNS服务器)之间的接口协议,譬如跨省之类的短信需要GNS的帮助指出当前ISMG该如何传递短信。
其中,后二方面属于移动短信息系统内部实现,对于SP来讲大概可以“透明”来看待,只要实现了SP同ISMG的正确交互,就可以实现接入移动梦网短信系统。我们关心的只是SP端的开发细节。
二、CMPP交互模式
从手机用户角度讲,按短信的发起/接收路径来讲,有两个叫法:
MT(Short Message Mobile Terminated, SMMT),短信接收,短信从SP发送到手机用户。
MO (Short Message Mobile Originate,SMMO),短信发送,短信从手机用户端发送到目标SP。
这两类短信交互,从SP端来看,都是属于Socket传输应用,CMPP的协议是以TCP/IP协议作为底层承载协议的,属于TCP/IP协议栈之上的应用。
SP同ISMG的交互连接分长连接短连接
所谓短连接,就是一次连接,传输一个消息,然后等待回复后拆除连接,显然,效率很低,所以,基本上不被考虑(实际应用移动也不允许SP采用短连接,只是不明白移动为什么还要写入文档? ISMG间会需要?)
所谓长连接,就是SP建立同ISMG连接,然后不断将数据包(一个个CMPP消息)发送到ISMG,此处发送不必等待某条消息的ISMG回应消息返回,就接着发送下一个消息。同时,等待ISMG返回信息或者等待ISMG发送给SP的消息。发送同接收消息不是一定要同步的,实际采用异步(同时也时双工)模式。从效率上,显然,必须全双工的异步模式才能够满足实际应用需求。
如下图(摘自CMPP2.0官方文档)所示,演示了长连接模式数据传输过程:
 
 
 
 
 
 
 

三、SP端开发
1.         消息分类
首先,图中的CMPP消息有很多种,SP同ISMG之间交流这些消息。大体上这些消息发出后,对方往往需要回复一个应答(RESP)类消息。注意,这些消息大多具有方向性,也就是说只能够从一端到另一端,而不可反方向进行,有些(少数)则可两端都能够发出。以下信息主要来源于移动的文档,但针对大家易混淆或源文档解释不够详细做了明确和补充。具体见下表:
       消息名
传递方向
解释说明
CMPP­_CONNECT
SP---àISMG
CMPP_CONNECT操作的目的是SP向ISMG注册作为一个合法SP身份,此消息需要向ISMG发出验证信息,验证方式采用md5加密密码方式,若注册成功后即建立了应用层的连接(否则ISMG会立即断开Socket),此后SP可以通过此ISMG接收和发送短信。
ISMG以CMPP_CONNECT_RESP消息响应SP的请求。具体的算法实现参考CMPP2.0文档和本文附件代码。
CMPP_CONNECT_RESP
SPß---ISMG
ISMG对CMPP_CONNECT消息的回复(无论是否验证成功);如果未通过,会在消息中包含参考信息,但ISMG会立即断开连接。
CMPP­_ACTIVE_TEST
SPßàISMG
这个消息通信双方都可以发出,目的是在没有其他消息发送时,保持双方的通信链路的连接,避免系统认为通信通道已经关闭。每一个收到此消息的实体应当返回CMPP_ACTIVE_TEST_RESP消息,以“礼节性”表示自己的还在通信,维持数据连接有效性。
不过,据网友交流,有些厂家实现的ISMG,仅仅靠自己发出此消息等待SP回答CMPP_ACTIVE_TEST_RESP来确定数据链路的有效性,而忽略SP的CMPP_ACTIVE_TEST消息(有些霸道吧?)这个值得注意,不要仅仅实现发送而不响应此消息,避免数据连接失效。
CMPP_ACTIVE_TEST_RESP
SPßàISMG
对通信的另一端的CMPP_ACTIVE_TEST消息的回复。作用参考CMPP_ACTIVE_TEST的解释。
CMPP­_SUBMIT
SP---àISMG
在正确建立了数据连接后,SP向ISMG发送一个SMS数据包。本消息需要仔细研究。接收到此消息后,ISMG需要以CMPP_SUBMIT_RESP消息作为回答。如果在一定时间时间内(移动给出的参考值60秒)内未得到消息回应,那么SP需要重新发送此数据包,以确保消息得到投递。如果重发达到3次后仍然得不到回应,SP端应该考虑可能ISMG已经失效,应当停止发送此短消息。
CMPP­_SUBMIT_RESP
SPß---ISMG
该消息由ISMG发送给SP,同时返回一个“收条”(源CMPP_SUBMIT消息的ISMG端的标示MSGID)给SP,表示“我ISMG已经确认收到你这条消息了”。收到此消息后,SP需要保留此“收条”,因为后面ISMG会最终报告本消息是否正确发送到用户手机。那个报告就是以此消息的“收条”作为确认那一条消息的。
CMPP_QUERY
SP---àISMG
这个查询不是查询单条消息的,是查询SP发送给ISMG的短信的业务情况。可以查总计数,还可以分类查询。(基本就是发起对移动sms业务数据库的查询统计)
CMPP_QUERY_RESP
SPß---ISMG
ISMG将查询的数据返回给SP。
CMPP_CANCEL
SP---àISMG
SP发起的取消某条消息的命令消息,其中包含了之前已经发送给ISMG消息的“收条”以便ISMG可以确定是那一条消息。如果消息已经发送给用户了,那么此消息/命令会无效,ISMG返回失败。
CMPP_CANCEL_RES
SPß---ISMG
ISMG返回的对CMPP_CANCEL的回复,并告知是否删除成功。
CMPP_DELIVER
SPß---ISMG
当有MO或者状态报告时,ISMG发送此消息。注意,此消息的数据可以是用户手机发送给SP的消息,也可是对于之前SP发送到ISMG的短信的最终状态的回复,报告短信的最终状态。
CMPP_DELIVER_RESP
SP---àISMG
SP礼节性的回复告知收到CMPP_DELIVER消息。要指出SP报告的CMPP_DELIVER消息的MSGID,以便ISMG知道那一条消息SP已经确认收到。
CMPP_TERMINAT
SPßàISMG
SP和ISMG都可以主动发消息给对方,自己这端由于某种原因需要终止当前的数据连接。终止后,要经过重新Connection(验证)之后才可以(进入事务阶段)发送SMS数据消息。
CMPP­_TERMINATE_RES
SPßàISMG
通知对方,本端已经最好撤除连接的准备。
                    
2.         交互阶段
整个CMPP协议交互分为验证事务两个阶段。验证阶段,发送CMPP_CONNECTION消息进行验证,通过验证后(必须要通过才)进入CMPP事务阶段,可以发送短信数据了。上表中的CMPP_CONNECTION以下的消息都属于事务阶段的消息。
 
3.         消息数据结构
每一个消息包含 消息头消息体两个部分,头固定长度为12字节,其他消息长度各异,但是同一类型消息的长度是固定的。所有消息的各个字段基本上仅有3种类型:Unsigned Integer (无符号整型)  、Integer(整型)、Octet String(字符串),每种类型具体长度不定,网络字节顺序。
1、  消息头(3个Unsigned Integer字段组成):
4字节的Total_Length (Unsigned Integer),包含了此消息的总计(包括了头部分)长度。
4字节的Command_Id(Unsigned Integer),指明了此消息到底是什么消息,就是上表中消息的枚举值。应用程序根据此值确定本数据包到底是什么消息,从而可以按照确定的消息类型,解析余下的消息体。
4字节的Sequence_Id(Unsigned Integer),指明了此数据包在发送此消息端的唯一编号。这个唯一编号,实际上可以看作流水操作编号。因为分析到交互模式我们看到,SP发送数据到ISMG,不是每发送一个就停下来等待ISMG的回复,而是“一下子”发送多个数据包过去,然后等待ISMG的回应。然而,怎么知道回应的消息是到底对应之前发送过去的消息中的那一条呢?本字段就是解决此难题。SP按照编号发送消息过去,等待ISMG的回应—一般情形下回应消息数据结构都有表明本消息回应的是SP发出的哪一条消息,这个对应就是依靠Sequence_Id。它并不要求一定要严格唯一,但是在给定的一段时间内,必须唯一(基本上只要SP发送过去的消息中没有重复就行了)。如果是需要SP回答的消息,SP也必须将ISMG发送过来的消息的Sequence_Id填入相应字段,表明这是某个消息的回应。SP端和ISMG端Sequence_ID都没有确定具体的算法。SP可以(但不推荐)采用数据库的唯一Id作为此值。
 
2、消息体。消息体长度根据消息不同,长度不一。其他的参考移动的文档《中国移动通信互联网短信网关接口协议(China Mobile Peer to Peer, CMPP)(V2.0)》,这里着重讲讲2个重要消息的消息体数据结构:
       CMPP­_SUBMIT的消息体:
字段名
长度(byte)
类型
描述
Msg_Id
8
Unsigned Integer
信息标识,应该由SP侧ISMG本身产生,本处填空,供ISMG传输时使用。SP提交时候应当留空。
Pk_total
1
Unsigned Integer
相同Msg_Id的信息总条数,从1开始。如果一条消息长度超多一条短信,可能需要分解成多条消息,那么实际上这多条消息属于一条完整消息,所以可以根据此给分解得到的多条短信进行编号,那么总计需要编成多少条短信,此处就填写多少。
 
Pk_number
1
Unsigned Integer
相同Msg_Id的信息序号,从1开始。编号决定消息的相对位置。
Registered_Delivery
1
Unsigned Integer
是否要求返回状态确认报告:
0:不需要
1:需要
2:产生SMC话单(该类型短信仅供网关计费使用,不发送给目的终端)。
一般情况下,都需要确认报告。SMC话单也需要返回是否成功的报告。这条消息用于包月SMC时,当你发送消息给移动的ISMG,移动的计费系统会一次性扣除用户的信息费,但是此消息不会送到用户手机。但是注意,有的ISMG厂商(很可能是移动要求)实现此消息时候,如果你并没有发送任何此包月类型的消息给用户手机,是不发生扣费行为的。移动会认为这是属于违规的“代收费”行为,会影响同移动的合作关系。
Msg_level
1
Unsigned Integer
信息级别,信息的优先级。不过实际当中,感觉ISMG端并没有区分优先级。
Service_Id
10
Octet String
业务类型,是数字、字母和符号的组合。这个表示业务的字符串可以给发出的短信分类。通过此字段大约可以知道每个服务项目的业务量,有利于统计和计费以及结算。
Fee_UserType
1
Unsigned Integer
计费用户类型字段
0:对目的终端MSISDN计费;
1:对源终端MSISDN计费;
2:对SP计费;
3:表示本字段无效,对谁计费参见Fee_terminal_Id字段。
Fee_terminal_Id
21
Unsigned Integer
被计费用户的号码(如本字节填空,则表示本字段无效,对谁计费参见Fee_UserType字段,本字段与Fee_UserType字段取0、1、2时互斥)
TP_pId
1
Unsigned Integer
GSM协议类型。详细是解释请参考GSM03.40中的9.2.3.9
TP_udhi
1
Unsigned Integer
GSM协议类型。详细是解释请参考GSM03.40中的9.2.3.23,仅使用1位,右对齐
Msg_Fmt
1
Unsigned Integer
信息格式
  0:ASCII串
  3:短信写卡操作
  4:二进制信息
  8:UCS2编码
15:含GB汉字  
这个决定了Msg_Content字段的字节内容应该按照什么编码来解码/编码。
Msg_src
6
Octet String
信息内容来源(SP的企业代码),例如919000。
FeeType
2
Octet String
资费类别
01:对“计费用户号码”免费
02:对“计费用户号码”按条计信息费
03:对“计费用户号码”按包月收信息费
04:对“计费用户号码”的信息费封顶
05:对“计费用户号码”的收费是由SP实现。
通常值为02,注意这是一个字符串,并非整型。
FeeCode
6
Octet String
资费代码(以分为单位),如:“0050”代表人民币0.50元。
ValId_Time
17
Octet String
存活有效期,格式遵循SMPP3.3协议
At_Time
17
Octet String
定时发送时间,格式遵循SMPP3.3协议。这个字段可以让短信在规定的时间给手机用户。一般情况下不填,保留为空字符串。
Src_Id
21
Octet String
源号码
SP的服务代码或前缀为服务代码的长号码, 网关将该号码完整的填到SMPP协议Submit_SM消息相应的source_addr字段,该号码最终在用户手机上显示为短消息的主叫号码。实际上就是服务代码,可以是长号码
DestUsr_tl
1
Unsigned Integer
接收信息的用户数量(小于100个用户),通常是1。移动是忌讳一条消息发给多个用户的。
Dest_terminal_Id
21*DestUsr_tl
Octet String
接收短信的MSISDN号码,一个类似字符串数组的结构。受DestUsr_tl的约束,决定了本字段的长度。
Msg_Length
1
Unsigned Integer
信息长度(Msg_Fmt值为0时:<160个字节;其它<=140个字节)。如果是ASCII码,可以达到160个英文字母。原因是因为英文字母仅占用7bit,而中文等双字节代码需要16位,同时每一个字节最高为都占用,所以最多140个字节,也就是70个汉字。
Msg_Content
Msg_length
Octet String
信息内容
Reserve
8
Octet String
保留
CMPP_SUBMIT消息长度是可变的,将SP端的消息发送给ISMG,ISMG将返回一个MSGID给SP标示此消息,之后(48小时以内,但一般最多几分钟内就可),ISMG返回关于此消息的递送报告。递送报告同MO短消息是通过另外一个重要消息CMPP­_DELIVER来提交给SP的:
CMPP­_DELIVER的各个字段:
字段名
字节数
属性
描述
Msg_Id
8
Unsigned Integer
信息标识
生成算法如下:
采用64位(8字节)的整数:
(1)时间(格式为MMDDHHMMSS,即月日时分秒):bit64~bit39,其中
bit64~bit61:月份的二进制表示;
bit60~bit56:日的二进制表示;
bit55~bit51:小时的二进制表示;
bit50~bit45:分的二进制表示;
bit44~bit39:秒的二进制表示;
(2)短信网关代码:bit38~bit17,把短信网关的代码转换为整数填写到该字段中。
(3)序列号:bit16~bit1,顺序增加,步长为1,循环使用。
各部分如不能填满,左补零,右对齐。
Dest_Id
21
Octet String
目的号码
SP的服务代码,一般4--6位,或者是前缀为服务代码的长号码;该号码是手机用户短消息的被叫号码。
Service_Id
10
Octet String
业务类型,是数字、字母和符号的组合。
TP_pid
1
Unsigned Integer
GSM协议类型。详细解释请参考GSM03.40中的9.2.3.9
TP_udhi
1
Unsigned Integer
GSM协议类型。详细解释请参考GSM03.40中的9.2.3.23,仅使用1位,右对齐
Msg_Fmt
1
Unsigned Integer
信息格式
  0:ASCII串
  3:短信写卡操作
  4:二进制信息
  8:UCS2编码
15:含GB汉字   
Src_terminal_Id
21
Octet String
源终端MSISDN号码(状态报告时填为CMPP_SUBMIT消息的目的终端号码)
Registered_Delivery
1
Unsigned Integer
是否为状态报告
0:非状态报告(MO SMS)
1:状态报告
此字段决定了CMPP­_DELIVER消息到底是手机上行一条消息到SP还是ISMG向SP报告之前发送的消息最终递送状态。
Msg_Length
1
Unsigned Integer
消息长度。是指Msg_Content字段的长度。
Msg_Content
Msg_length
Octet String
消息内容。如果消息不是状态报告,那么按照Msg_Fmt指示解码为特定编码的字符串内容。
Reserved
8
Octet String
保留项
如果是报告,那么Msg_Content将按照状态报告结构来解释:
字段名
字节数
属性
描述
Msg_Id
8
Unsigned Integer
信息标识
SP提交短信(CMPP_SUBMIT)操作时,与SP相连的ISMG产生的Msg_Id。
这个MSGID实际上就是SP之前发送一个CMPP_SUBMIT消息之后的CMPP_SUBMIT_RESP消息中返回的关于CMPP_SUBMIT消息的ISMG编号.,根据此MSGID可以知道那条消息最终确定的递送状态。
Stat
7
Octet String
发送短信的应答结果,含义与SMPP协议要求中stat字段定义相同,详见下面。SP根据该字段确定被报告的CMPP_SUBMIT消息的处理状态。
Submit_time
10
Octet String
YYMMDDHHMM(YY为年的后两位00-99,MM:01-12,DD:01-31,HH:00-23,MM:00-59)
Done_time
10
Octet String
YYMMDDHHMM
Dest_terminal_Id
21
Octet String
目的终端MSISDN号码(SP发送CMPP_SUBMIT消息的目标终端)
SMSC_sequence
4
Unsigned Integer
取自SMSC发送状态报告的消息体中的消息标识。
关于State字段,如下解释:
消息状态名
最终状态
描述
DELIVERED
DELIVRD
消息到达目标
EXPIRED
EXPIRED
消息过期
DELETED
DELETED
消息被删除
UNDELIVERABLE
UNDELIV
消息未被送达
ACCEPTED
ACCEPTD
消息被认可
UNKNOWN
UNKNOWN
未知状态
REJECTED
REJECTD
消息被弹回
其他消息结构,具体说明见中移动的CMPP协议。
 
4.         安全验证
CMPP协议在CMPP_CONNECT中传递验证消息。验证消息为9字节的0+移动给出的密码+当前时间戳字节数组的MD5算法后的字节。时间戳为 月日时分秒,10位。代码算法如下:
private byte[] getMd5Code()
{
       byte[] buf=new byte[6+9+_Password.Length+10] ;   
       byte[] s_a=Encoding.ASCII.GetBytes(_SystemID); //就是企业代码
       byte[] s_0={0,0,0,0,0,0,0,0,0};     //9字节的0,此处当作右补0
       byte[] s_p=Encoding.ASCII.GetBytes(_Password); //密码
       this._timestamp =getTimestamp();    //取得认证码时赋值字符串
       byte[] s_t=Encoding.ASCII.GetBytes(_timestamp); //10位字符串字节数组
       s_a.CopyTo(buf,0);   
       s_0.CopyTo(buf,6);  
       s_p.CopyTo(buf,6+9);  
       s_t.CopyTo(buf,6+9+_Password.Length);
       MD5 md5= new MD5CryptoServiceProvider(); //创建MD5类别
       return(md5.ComputeHash(buf,0,buf.Length));
}   
其中getTimestamp函数为返回例如“0710125959”(7月10号12点59分59秒)这样的字符串,详细代码略过,有兴趣请查看本文的附件代码。
 
5.         厂商API问题
笔者公司所处广东,广东移动提供了华为的以C 形式的API(SMEIDLL.dll),来帮助大家初期熟悉CMPP协议。但是,经过开发测试,发现华为的API至少存在几个问题:
1、  封装成几个API函数,但是由于CMPP自身的复杂性,导致这些函数丑陋无比,参数多,而且难以明晰含义。华为的API,内部将CMPP的验证、事务阶段分成几个函数实现,其中将发送SMS到ISMG功能以函数提供,竟然出现SubmitAExExEx之类的函数说明。
2、  CMPP的交互是异步的,需要多线程实现一边发送,一边接收反馈信息。此API应当是内部维护一个线程进行CMPP_SUBMIT消息发送,但是华为API却通过空循环之类的操作等待ISMG返回CMPP_SUBMIT_RESP得到相应的MSGID再返回(从而实现消息同步返回)。经过测试,大约需要200毫秒,这个在实际SP的高性能需求场合根本无法满足系统要求。
3、  接收短信必须依靠程序主动先发出函数HasDeliverMessage调用 ,得到有消息才可通过GetDeliverSMEx函数获取消息,显然,这种方式是低效率的,而且容易产生消息数据包丢失,表现为有些MO消息,SP接收不到。而且,令人疑惑的是,你还不能够新开一个线程专门来做判断并接收MO的动作,实际开发中一旦采用线程来做就回发生内存保护错误(大概属于同API自身的线程有冲突)。
4、  返回错误码,往往又是华为自己定的一套错误码(大概华为设计此API为了适应SMGP CMPP等多个协议),而且经常变动,很是伤脑筋。
基于以上理由,我认为自己按照CMPP协议开发一个SP端程序,比较能够满足一般SP的需求。
 
四、C#实现
1、CMPP协议实现类CMPPClient
通过研究,笔者用C#写了一组类实现自己的CMPP SP端程序(CMPPClient)。为了实现相关类,还需要编写一些辅助类,并且首先要解决CMPP协议的数据结构同C#的数据之间的转换问题。
CMPP的Octet String 实际上相当于C#中的byte[],所有CMPP消息的Octet String字段出了CMPP_SUBMIT和CMPP_DELIVER的msg_content字段外,其他的都可以认为是ASCII编码,所以全部可以采用System.Text.Encoding.ASCII进行编码和解码;对于Msg_Content字段,由于一般情况下存在汉字信息传输.,所以默认的编/解码应该为Encoding.Default,实际是什么编码还要考察MSG_Fmt字段指示正文到底是什么编码。
对于Unsigned Integer 和Interger字段,需要按照网络字节顺序和x86机器的字节编码顺序对照关系进行转换,具体我设计了一个工具类提一些转换方法使用:
public class BIConvert  //字节 整形 转换类 网络格式转换为内存格式
       {
              public static byte[] Int2Bytes(uint i)  //转换整形数据的网络次序字节数组
              {
                     byte[] t=BitConverter.GetBytes(i) ;
                     byte b=t[0];
                     t[0]=t[3];
                     t[3]=b;
                     b=t[1];
                     t[1]=t[2];
                     t[2]=b;
                     return(t);
              }
 
              public static uint Bytes2UInt(byte[] bs,int startIndex) //返回字节数组代表的整数数字,4个字节长度的数组
              {
                     byte[] t=new byte[4];
                     for(int i=0;i<4 && i< bs.Length-startIndex ;i++)
                     {
                            t[i]=bs[startIndex+i];
                     } 
                     byte b=t[0];
                     t[0]=t[3];
                     t[3]=b;
                     b=t[1];
                     t[1]=t[2];
                     t[2]=b;
                     return(BitConverter.ToUInt32(t,0));
              }
 
              public static uint Bytes2UInt(byte[] bs)  //没有指定起始索引
              {
                     return( Bytes2UInt(bs,0));
              }
       }
 
其次,为了实现收发数据的“全双工”,需要设计至少两个线程处理socket的读取和数据包写入。另外,为了自动实现对数据链路的保持,以及自动实现数据包重发机值,我还增加了一个值守线程,自动处理以上问题。详细见后代码。另外,消息中有很多同时间有关的字段,但是这些时间相关字段并非按照统一规格编码的,这个需要仔细研究协议或者实现代码。
其三,为了解析/编码数据包方便,我将SP端涉及到的消息以类的形式实现,根据具体的消息类型,将数据包字节解析还原为特定的消息;另一方面,当需要发送一些消息时,将消息的各个字段,根据类型和编码类型“组装”成字节数组,以便Socket能够发送出去。
其四,为了达到及时处理短消息的收发,我大量采用了C#事件触发来达到消息通知目的,而且,也设计一组事件参数,供事件的具体监听者可以掌握需要的信息。
其五,需要注意多线程间的同步问题。
其六,填写CMPP_SUBMIT消息需要注意内容编码、计费字段正确填写
 
2、事件模型
大体上实现了十多个事件,这些事件具体为:
当CMPP_DELIVER消息送来的是短消息送达报告时,发生消息送达报告事件:
public delegate void ReportEventHandler(object sender, ReportEventArgs e); 
       当CMPP_DELIVER消息送来的是用户手机MO短消息时,发生短信到达事件,其他程序可以在处理此事件获得消息的正文、手机号码、SP服务号码等信息:
public delegate void SMSEventHandler(object sender, SMSEventArgs e);  
       当ISMG发出CMPP_TERMINATE消息时,发生,具体的回应,我在具体实现中先自动进行了回复,产生此事件仅仅向外部程序(此事件的截取者)表达收到此消息,需要进行“善后清场”操作:
       public delegate void TerminateEventHandler(object sender,TerminateEventArgs e);  
              当SP主动终止连接时,发出CMPP_TERMINATE消息,ISMG会响应CMPP_TERMINATE_RESP消息,此事件表示收到此回应
       public delegate void TerminateRespEventHandler(object sender,TerminateRespEventArgs e); 
       以下两个事件针对链路保持消息CMPP_ACTIVE_TEST及CMPP_ACTIVE_TEST_RESP发生:
       public delegate void TestEventHandler(object sender,TestEventArgs e);
       public delegate void TestRespEventHandler(object sender,TestRespEventArgs e);
       SP发出CMPP_CONNECT消息后,ISMG验证,然后发出CMPP_CONNECT_RESP消息,此时激活此事件:
       public delegate void ConnectRespEventHandler(object sender,ConnectRespEventArgs e);
       SP取消某条端消息,发出CMPP_CANCEL后,ISMG响应此消息返回CMPP_CANCEL_RESP消息时,激活事件:
       public delegate void CancelRespEventHandler(object sender,CancelRespEventArgs e);
       SP提交短信后,ISMG返回一个CMPP_SUBMIT_RESP 消息,包含“收条”(MSG_ID)在内,触发此事件:
       public delegate void SubmitRespEventHandler(object sender,SubmitRespEventArgs e);
       查询ISMG返回消息后,发生:
       public delegate void QueryRespEventHandler(object sender,QueryRespEventArgs e);
       当SP验证通过后,作为应用逻辑需要得到通知,我特此加了此事件:
       public delegate void LogonSuccEventHandler(object sender,EventArgs e); //当成功登录系统
       以下事件,不是基于CMPP消息,而是根据SP同ISMG消息队列扫描后判断触发事件:
       public delegate void SocketClosedEventHandler(object sender,EventArgs e); //当套接字被检测到关闭
       public delegate void FailedItemDeletedEventHandler(object sender,WaitingQueueItemEventArgs e); //当一条存在于等待队列的消息超过60秒没有回应
以上这些事件缺省实现保证了SP端CMPP客户对于ISMG的响应自动化,提供触发事件保证调用此客户端类的系统可以通过事件发生准确的控制SP端的内部状态,获取交互信息。
另一方面,由于这些大多数事件发生于数据包达到后的处理,实际上需要处理事件程序一定要“迅速”解决,或者干脆将消息转换为可以暂存的消息形式,由其他程序进一步处理。CMPP的SP端要满足大量短信息应用需求,就必须严格控制消息交互处理逻辑不要太复杂,特别不要设计大量I/O处理;如果实在要处理,最好采用异步线程的方式来处理。
 
3、为了提高效率,开了3个线程:
       RecvISMGMsgThread   用于处理接收ISMG发送过来的消息,并根据消息、消息解析后的字段内容触发相应的事件。
       SendSPMsgThread        用于处理向ISMG发送数据包。注意,有些消息(譬如CMPP_ACTIVE_TEST)是系统自己产生的。另外,有些消息是收到ISMG的消息后需要立即回应给ISMG的,那么这些消息,全部进入内部维护的消息队列(_outSeqQueue)。该队列会自动排序消息,所有需要发送的消息,进入此队列,本线程不断从队列取出需要发送的消息,转换成数据包,通过Socket发送到ISMG.
       DeamonThread      用于监测数据连接socket是否可用,是否需要发出维持数据连接的测试数据包;有些消息发送过去了,过了协议规定的时间仍然没有收到RESP消息,那么需要将消息从已经发送的队列中提取,重新加入到发送队列中,排队后等待送出。
       可以仔细分析提供的代码,研究其中的具体实现。
 
五、问题小结
根据自己的经验,觉得以下几点对于整个系统开发较为重要:
1、一定要正确理解协议。
很多网友交流时候,总抱怨协议滥,搞不定,其实很多原因属于自己没有清楚理解协议。从我的接触的移动ISMG来看,应该说实现协议还是很严格遵守CMPP的描述。倒是,一些网友自己开发的模拟器不是很规范(不是批评,郑重声明),需要自己在开发时候引起注意。
2、多线程互斥问题。
多个线程之间涉及一些队列的操作,需要进行同步锁定,否则容易引起问题。出现异常也需要及时捕获,并纪录作为错误信息参考,便于排除bug。
3、自己控制数据包流向和处理时间。
由于设计目标是高性能,所以在处理socket数据读写时候要注意对于一些事件处理不要过多消耗系统资源,避免引起数据包来不及处理而导致数据丢失。特别在数据繁忙时刻往往会使ISMG的吞吐性能下降,需要考虑对这种情况下的流量控制。有时,你不能够指望ISMG如你所愿及时回应你,更为常见的是ISMG根本不返回RESP类型的数据包。另外,本协议处理数据收发采用阻塞Socket,有网友建议我采用异步非阻塞Socket,我想可能异步非阻塞Socket会更好。
4、服务监控问题。
由于一些意外,往往会导致数据连接被中断,这是,需要建立超时重建连接的机制。我给出的例子并未很好解决,希望其他方家指正。
5、字节顺序问题。
这个问题,对于初接触socket编程的人士往往造成很大麻烦。不过,CMPP协议设计的基本数据类型很简单,仅需要按照本例子参考即可解决。
6、具体协议应用问题。
本例仅仅是一个按照协议要求实现CMPP协议的类,完整的SP端方案需要结合自己公司的实际要求,改造或者重用本例。限于篇幅,文中仅仅能够列出重点片断,详细细节清参考供下载的代码(附注释)。本文仅仅是针对CMPP2.0协议进行讨论开发,协议的详情请从移动梦网(http://www.monternet.com/moneditor/cs/SP/cmcc/)下载。其他技术参考可以到如下处获取,也可察看网友的贴字获取进一步详细说明:
       天堂鸟交流论坛(http://www.spzone.net/bbs/index.asp
       CSDN社区 移动平台
(http://community.csdn.net/expert/forum.asp?url=/Expert/ForumsList.asp?roomid=63&typenum=1&whichpage=1)
       SP论坛(http://www.spforum.net/jishu/Index.asp)
如果你发现本人的实例存在问题(我想那简直是一定的了),请不吝赐教myjobsdk@yahoo.com.cn。
 
 后记:现在CMPP3。0也就是移动的MISC平台版本的需要订购关系确定才可以计费。不过不是本文讨论的重点。

posted @ 2012-07-19 01:46 火炎炎 阅读(1167) | 评论 (0)编辑 收藏

首先明确闭包的概念:一个代码段被用来做为方法的参数.
java中没有直接使用某个方法做为另一个方法的参数的,java使用匿名内部类来模拟这种情况。

匿名内部类往往是做为一个内部类(接口)的具体实现。在一些平台类(platform class)中有一些模板方法。模板方法的包含了固定流程。其中某些步骤是调用了内部类(接口)中的某些方法。但是平台类将这些方法的具体实现延迟到了业务类中。业务类调用平台类的模板方法,但是传入匿名内部类的实现做为模板方法的参数。

示例:

 

package callback;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class AnonymousBusinessTemplateExample2 {

// 内部接口也是回调接口,只定义抽象方法。
private interface Callback {
Object doIt(Connection conn)
throws SQLException;
}

// 模板方法(抽象)
private Object execute(Callback callback) throws SQLException {
Connection conn
= openConnection();
try {
return callback.doIt(conn);
}
finally {
closeConnection(conn);
}
}

// 业务方法(具体)
public Object sqlQuery(final String sql) throws SQLException {
//匿名内部类做为模板方法的参数来模拟闭包
return execute(new Callback() {
public Object doIt(Connection conn) throws SQLException {
return conn.createStatement().executeQuery(sql);
}
});
}

public Connection openConnection() throws SQLException {
return DriverManager.getConnection("", null);
}

public void closeConnection(Connection conn) throws SQLException {
if (conn != null && !conn.isClosed()) {
conn.close();
}
}
}

 

 

 

一般内部接口比内部类用的更多。内部类中的方法可以有默认的实现。匿名内部类做为业务方法的参数传入时,会override默认的方法。内部接口的话,没有默认的实现。完全将具体的实现交给了匿名内部类。

匿名内部类的思想是回调,即好茉坞原则。回调的一个好处是decouple。 客户端只需要关心业务(比如匿名内部类的具体实现)而不用再关心一些资源的连接释放什么的,这些交给平台类中的模板方法。ruby的闭包还支持对数组中的每个元素,文件中的每行,结果集中的每个记录的操作。而用java实现这样的迭代并操作其中元素非常麻烦。感觉java中用的多的偏模板方法,即逻辑中固定一些流程,初始化及释放某些资源。






动态回调函数、匿名内部类和spring中的excute方法

    公司目前采用了spring框架来构建和管理整个web项目。对于持久层的处理,使用了由spring框架提供的对hibernate3的封装。这样做无非是为了使用spring提供的对事务的统一管理。当我们用到由spring所封装的hibernate的时候一定会用到一个类:HibernateTemplate.这是一个对持久层处理封装的非常完整的类,包括对session的管理(事实上session的获取于释放是一个令人头疼的问题)等等,我们通常会使用HibernateTemplate的excute方法来进行数据库操作(即使我们调用的也许是别的类似于find、get之类的方法,但是实质上最终还是转变为了调用excute方法)。对于第一次使用这个方法一定会存在困扰。因为excute方法所需要的参数一个HibernateCallback类型的参数。而在excute方法体内部回调了HibernateCallback类型的doInHibernate方法。这是一个典型的对象回调。就到目前为止也许一切都很清晰。但是实际上如果阅读了HibernateTemplate的内部代码就会发现,对于像get、find这样的方法最终都回调用excute来完成数据库操作但是调用形式看起来却很奇怪:

public Object get(final Class entityClass, final Serializable id, final LockMode lockMode)

        throws DataAccessException

    {

        return execute(new HibernateCallback() {

            public Object doInHibernate(Session session)

                throws HibernateException

            {

                if(lockMode != null)

                    return session.get(entityClass, id, lockMode);

                else

                    return session.get(entityClass, id);

            }

        }, true);

    }

    Excute方法的参数是一种匿名类的方式。为什么要采用匿名类呢(不管怎么说匿名类看起来总是让人觉得不舒服)?这个地方是否必须采用匿名类呢?

    首先我们来想一想这段代码涉及到几个关键点:1、回调:excute方法会回调HibernateCallback类型的doInHibernate方法;2、匿名类参数:我们为excute方法提供的参数并不是一个真正生命出来的HibernateCallback实例。3、动态创建回调方法:如果我们打开HibernateCallback类就会发现,其实这是一个abstract类型的类,而他声明了唯一的一个ie抽象方法就是doInHibernate。问题似乎已经明朗了,如果不采用匿名类,我们需要做的是为HibernateCallback创建一个实现类,并且实现doInHibernate方法。但是最要命的问题是doInHibernate方法的实现对于我们的实际需求来说每一次调用可能都是不一样的(在doInHibernate方法中我们使用session进行数据库操作,对于不同的业务逻辑,方法实现必定是不一样的),采用了匿名类我们不用在代码重创建新的类型,而且可以动态的创建我们所需要的回调函数。

    总结一下,我们上面所讲的并非是如何使用HibernateTemplate这个类。我们得到的结论是:当我们需要动态的创建回调函数的时候,匿名内部类是一个好的方式。注:这种需要动态创建的回调方法通常是一个interface中的接口或者abstract class中的抽象方法。


posted @ 2011-11-24 16:44 火炎炎 阅读(870) | 评论 (0)编辑 收藏