技术开发文章选读

联通VAC定制包解析userIdType和updateType数据碰到的一个问题解决

发布者:罗堃,发布时间:2009-5-15 上午6:33

因为没有使用java平台,也就没有使用建议的axis对提供的wsdl文件做处理,所有关于数据包的解析只能自己写程序做解析处理。
华为提供的vac请求样例包request.xml

  1. <soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://soap.bossagent.vac.unicom.com">
  2.    <soapenv:Header/>
  3.    <soapenv:Body>
  4.       <soap:orderRelationUpdateNotify soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  5.          <orderRelationUpdateNotifyRequest xsi:type="req:OrderRelationUpdateNotifyRequest" xmlns:req="http://req.sync.soap.bossagent.vac.unicom.com">
  6.             <recordSequenceId xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">11</recordSequenceId>
  7.             <userIdType xsi:type="soapenc:int" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">1</userIdType>
  8.             <userId xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">1</userId>
  9.             <serviceType xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">1</serviceType>
  10.             <spId xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">1</spId>
  11.             <productId xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">2</productId>
  12.             <updateType xsi:type="soapenc:int" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">3</updateType>
  13.             <updateTime xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">44444444</updateTime>
  14.             <updateDesc xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">444</updateDesc>
  15.             <linkId xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">44</linkId>
  16.             <content xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">?</content>
  17.             <effectiveDate xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">333333</effectiveDate>
  18.             <expireDate xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">2222222</expireDate>
  19.             <time_stamp xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">3333</time_stamp>
  20.             <encodeStr xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">111gg</encodeStr>
  21.          </orderRelationUpdateNotifyRequest>
  22.       </soap:orderRelationUpdateNotify>
  23.    </soapenv:Body>
  24. </soapenv:Envelope>

实际在网络上收到的数据包

  1. <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  2.  <soapenv:Body>
  3.   <ns1:orderRelationUpdateNotify soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://soap.bossagent.vac.unicom.com">
  4.    <orderRelationUpdateNotifyRequest href="#id0"/>
  5.   </ns1:orderRelationUpdateNotify>
  6.   <multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns2:OrderRelationUpdateNotifyRequest" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns2="http://req.sync.soap.bossagent.vac.unicom.com">
  7.    <recordSequenceId xsi:type="soapenc:string">200903181622360043</recordSequenceId>
  8.    <userIdType href="#id1"/>
  9.    <userId xsi:type="soapenc:string">8613004849965</userId>
  10.    <serviceType xsi:type="soapenc:string">5</serviceType>
  11.    <spId xsi:type="soapenc:string">90000001</spId>
  12.    <productId xsi:type="soapenc:string">050002</productId>
  13.    <updateType href="#id2"/>
  14.    <updateTime xsi:type="soapenc:string">20090313110400</updateTime>
  15.    <updateDesc xsi:type="soapenc:string"></updateDesc>
  16.    <linkId xsi:type="soapenc:string"></linkId>
  17.    <content xsi:type="soapenc:string"></content>
  18.    <effectiveDate xsi:type="soapenc:string">20080301000000</effectiveDate>
  19.    <expireDate xsi:type="soapenc:string">20100301000000</expireDate>
  20.    <time_stamp xsi:type="soapenc:string">0318162236</time_stamp>
  21.    <encodeStr xsi:type="soapenc:string"></encodeStr>
  22.   </multiRef>
  23.   <multiRef id="id2" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:int" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">1</multiRef>
  24.   <multiRef id="id1" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:int" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">1</multiRef>
  25.  </soapenv:Body>
  26. </soapenv:Envelope>

通过对数据包的分析,可以看到在实际包里面,采用了multiRef方式,对userIdType和updateType数据的值使用了href引 用,这样在分析数据包时,就同时要根据节点的href属性值来判断处理所引用的值节点,然后再遍历multiRef的节点找匹配id属性值的,从而找出正 确的值所在。

——-
虽然现在解析正确了,但是没有使用标准的xml开发包工具开发,缺少开发包所做的类型验证,数据包还原等处理,如果以后接口数据格式有发生变化还是要再次 进行解析代码的修改处理,不过不想只为了一个xml解析目的就为服务器添加上java的一堆环境和东西,只好先忍受着自己手工分析xml数据吧。

TCP: SYN ACK FIN RST PSH URG 详解

发布者:罗堃,发布时间:2009-4-27 下午7:02

三次握手Three-way Handshake

一个虚拟连接的建立是通过三次握手来实现的

1. (B) --> [SYN] --> (A)

假如服务器A和客户机B通讯. 当A要和B通信时,B首先向A发一个SYN (Synchronize) 标记的包,告诉A请求建立连接.

注意: 一个 SYN包就是仅SYN标记设为1的TCP包(参见TCP包头Resources). 认识到这点很重要,只有当A受到B发来的SYN包,才可建立连接,除此之外别无他法。因此,如果你的防火墙丢弃所有的发往外网接口的SYN包,那么你将不能让外部任何主机主动建立连接。

2. (B) <-- [SYN/ACK] <--(A)

接着,A收到后会发一个对SYN包的确认包(SYN/ACK)回去,表示对第一个SYN包的确认,并继续握手操作.

注意: SYN/ACK包是仅SYN 和 ACK 标记为1的包.

3. (B) --> [ACK] --> (A)

B收到SYN/ACK 包,B发一个确认包(ACK),通知A连接已建立。至此,三次握手完成,一个TCP连接完成

Note: ACK包就是仅ACK 标记设为1的TCP包. 需要注意的是当三此握手完成、连接建立以后,TCP连接的每个包都会设置ACK位

这就是为何连接跟踪很重要的原因了. 没有连接跟踪,防火墙将无法判断收到的ACK包是否属于一个已经建立的连接.一般的包过滤(Ipchains)收到ACK包时,会让它通过(这绝对不是个好主意). 而当状态型防火墙收到此种包时,它会先在连接表中查找是否属于哪个已建连接,否则丢弃该包

四次握手Four-way Handshake

四次握手用来关闭已建立的TCP连接

1. (B) --> ACK/FIN --> (A)

2. (B) <-- ACK <-- (A)

3. (B) <-- ACK/FIN <-- (A)

4. (B) --> ACK --> (A)

注意: 由于TCP连接是双向连接, 因此关闭连接需要在两个方向上做。ACK/FIN 包(ACK 和FIN 标记设为1)通常被认为是FIN(终结)包.然而, 由于连接还没有关闭, FIN包总是打上ACK标记. 没有ACK标记而仅有FIN标记的包不是合法的包,并且通常被认为是恶意的

连接复位Resetting a connection

四次握手不是关闭TCP连接的唯一方法. 有时,如果主机需要尽快关闭连接(或连接超时,端口或主机不可达),RST (Reset)包将被发送. 注意在,由于RST包不是TCP连接中的必须部分, 可以只发送RST包(即不带ACK标记). 但在正常的TCP连接中RST包可以带ACK确认标记

请注意RST包是可以不要收到方确认的?

无效的TCP标记Invalid TCP Flags

到目前为止,你已经看到了 SYN, ACK, FIN, 和RST 标记. 另外,还有PSH (Push) 和URG (Urgent)标记.

最常见的非法组合是SYN/FIN 包. 注意:由于 SYN包是用来初始化连接的, 它不可能和 FIN和RST标记一起出现. 这也是一个恶意攻击.

由于现在大多数防火墙已知 SYN/FIN 包, 别的一些组合,例如SYN/FIN/PSH, SYN/FIN/RST, SYN/FIN/RST/PSH。很明显,当网络中出现这种包时,很你的网络肯定受到攻击了。

别的已知的非法包有FIN (无ACK标记)和"NULL"包。如同早先讨论的,由于ACK/FIN包的出现是为了关闭一个TCP连接,那么正常的FIN包总是带有 ACK 标记。"NULL"包就是没有任何TCP标记的包(URG,ACK,PSH,RST,SYN,FIN都为0)。

到目前为止,正常的网络活动下,TCP协议栈不可能产生带有上面提到的任何一种标记组合的TCP包。当你发现这些不正常的包时,肯定有人对你的网络不怀好意。

UDP (用户数据包协议User Datagram Protocol)
TCP是面向连接的,而UDP是非连接的协议。UDP没有对接受进行确认的标记和确认机制。对丢包的处理是在应用层来完成的。(or accidental arrival).

此处需要重点注意的事情是:在正常情况下,当UDP包到达一个关闭的端口时,会返回一个UDP复位包。由于UDP是非面向连接的, 因此没有任何确认信息来确认包是否正确到达目的地。因此如果你的防火墙丢弃UDP包,它会开放所有的UDP端口(?)。

由于Internet上正常情况下一些包将被丢弃,甚至某些发往已关闭端口(非防火墙的)的UDP包将不会到达目的,它们将返回一个复位UDP包。

因为这个原因,UDP端口扫描总是不精确、不可靠的。

看起来大UDP包的碎片是常见的DOS (Denial of Service)攻击的常见形式 (这里有个DOS攻击的例子,http://grc.com/dos/grcdos.htm ).

ICMP (网间控制消息协议Internet Control Message Protocol)
如同名字一样, ICMP用来在主机/路由器之间传递控制信息的协议。 ICMP包可以包含诊断信息(ping, traceroute - 注意目前unix系统中的traceroute用UDP包而不是ICMP),错误信息(网络/主机/端口 不可达 network/host/port unreachable), 信息(时间戳timestamp, 地址掩码address mask request, etc.),或控制信息 (source quench, redirect, etc.) 。

你可以在http://www.iana.org/assignments/icmp-parameters中找到ICMP包的类型。

尽管ICMP通常是无害的,还是有些类型的ICMP信息需要丢弃。

Redirect (5), Alternate Host Address (6), Router Advertisement (9) 能用来转发通讯。

Echo (8), Timestamp (13) and Address Mask Request (17) 能用来分别判断主机是否起来,本地时间和地址掩码。注意它们是和返回的信息类别有关的。它们自己本身是不能被利用的,但它们泄露出的信息对攻击者是有用的。

ICMP消息有时也被用来作为DOS攻击的一部分(例如:洪水ping flood ping,死 ping ?呵呵,有趣 ping of death)?/p>

包碎片注意A Note About Packet Fragmentation

如果一个包的大小超过了TCP的最大段长度MSS (Maximum Segment Size) 或MTU (Maximum Transmission Unit),能够把此包发往目的的唯一方法是把此包分片。由于包分片是正常的,它可以被利用来做恶意的攻击。

因为分片的包的第一个分片包含一个包头,若没有包分片的重组功能,包过滤器不可能检测附加的包分片。典型的攻击Typical attacks involve in overlapping the packet data in which packet header is 典型的攻击Typical attacks involve in overlapping the packet data in which packet header isnormal until is it overwritten with different destination IP (or port) thereby bypassing firewall rules。包分片能作为 DOS 攻击的一部分,它可以crash older IP stacks 或涨死CPU连接能力。

Netfilter/Iptables中的连接跟踪代码能自动做分片重组。它仍有弱点,可能受到饱和连接攻击,可以把CPU资源耗光。

握手阶段:
序号 方向 seq ack
1  A->B 10000 0
2 B->A 20000 10000+1=10001
3 A->B 10001 20000+1=20001
解释:
1:A向B发起连接请求,以一个随机数初始化A的seq,这里假设为10000,此时ACK=0

2:B收到A的连接请求后,也以一个随机数初始化B的seq,这里假设为20000,意思是:你的请求我已收到,我这方的数据流就从这个数开始。B的ACK是A的seq加1,即10000+1=10001

3:A收到B的回复后,它的seq是它的上个请求的seq加1,即10000+1=10001,意思也是:你的回复我收到了,我这方的数据流就从这个数开始。A此时的ACK是B的seq加1,即20000+1=20001


数据传输阶段:
序号  方向      seq ack size
23 A->B 40000 70000 1514
24 B->A 70000 40000+1514-54=41460 54
25 A->B 41460 70000+54-54=70000 1514
26 B->A 70000 41460+1514-54=42920 54
解释:
23:B接收到A发来的seq=40000,ack=70000,size=1514的数据包
24:于是B向A也发一个数据包,告诉B,你的上个包我收到了。B的seq就以它收到的数据包的ACK填充,ACK是它收到的数据包的SEQ加上数据包的大小(不包括以太网协议头,IP头,TCP头),以证实B发过来的数据全收到了。
25:A在收到B发过来的ack为41460的数据包时,一看到41460,正好是它的上个数据包的seq加上包的大小,就明白,上次发送的数据包已安全到达。于是它再发一个数据包给B。这个正在发送的数据包的seq也以它收到的数据包的ACK填充,ACK就以它收到的数据包的seq(70000)加上包的size(54)填充,即ack=70000+54-54(全是头长,没数据项)。

其实在握手和结束时确认号应该是对方序列号加1,传输数据时则是对方序列号加上对方携带应用层数据的长度.如果从以太网包返回来计算所加的长度,就嫌走弯路了.
另外,如果对方没有数据过来,则自己的确认号不变,序列号为上次的序列号加上本次应用层数据发送长度.

JDBC中文字符问题根本解决方法。

发布者:罗堃,发布时间:2009-3-25 下午9:42   [ 更新时间:2009-3-27 上午12:22 ]

在使用JDBC处理Oracle中文的时候,会有很多复杂的问题。为了不出现乱码,总是需要转码。这种转换在换了个Oracle数据库字符集后,很可能又出现其他的问题。只有通过Oracle JDBC的本地化字段才可以彻底解决这些问题。以下的方法至少适合于MySQL、MSSQLServer和Oracle。
 
需要的数据包:
import oracle.sql.*;
import oracle.jdbc.*;
提取数据的处理:
  //Check jdbc type.
  if(JDBCType.isOracle(getJDBCType()))
  {
   //Get value.
   CHAR value = ((OracleResultSet)resultSet).getCHAR("subject");
   //Get content.
   request.subject = value != null ? value.stringValue() : null;
  }
  //Get bytes.
  else request.subject = CharsetConverter.convert(resultSet.getBytes("subject"),getCharset());
插入数据的处理:
  //Check jdbc type.
  if(JDBCType.isOracle(getJDBCType()))
  {
   //Create char.
   CHAR value = new CHAR(request.subject,OracleCharacterSet.getCharacterSet(getCharset()));
   //Set content.
   ((OraclePreparedStatement)insertStatement).setCHAR(11,value);
  }
  //Set subject.
  else insertStatement.setBytes(11,CharsetConverter.convert(request.subject,getCharset()));
 
字符集变换函数:
public class OracleCharacterSet
{
 /**
  * Get character set.
  *
  * @param charset Charset.
  * @return
  *     <p>Character set of oracle.</p>
  */
 public static CharacterSet getCharacterSet(String charset)
 {
  //Oracle id.
  int oracleID;
  //Check result.
  if(charset == null || charset.length() <= 0) oracleID = CharacterSet.DEFAULT_CHARSET;
  else if(charset.equals("ASCII")) oracleID = CharacterSet.ASCII_CHARSET;
  else if(charset.equals("ISO_LATIN_1")) oracleID = CharacterSet.ISO_LATIN_1_CHARSET;
  else if(charset.equals("UNICODE_1")) oracleID = CharacterSet.UNICODE_1_CHARSET;
.....
  else if(charset.equals("ZHS16GBK")) oracleID = CharacterSet.ZHS16GBK_CHARSET;
  else if(charset.equals("ZHS16DBCS")) oracleID = CharacterSet.ZHS16DBCS_CHARSET;
  else if(charset.equals("ZHS32GB18030")) oracleID = CharacterSet.ZHS32GB18030;
.....
  else return null;
  //Return result.
  return CharacterSet.make(oracleID);
 }
}

理解ORACLE数据库字符集

发布者:罗堃,发布时间:2009-3-25 上午6:00

一.引言

    ORACLE数据库字符集,即Oracle全球化支持(Globalization Support),或即国家语言支持(NLS)其作用是用本国语言和格式来存储、处理和检索数据。利用全球化支持,ORACLE为用户提供自己熟悉的数据库母语环境,诸如日期格式、数字格式和存储序列等。Oracle可以支持多种语言及字符集,其中oracle8i支持48种语言、76个国家地域、229种字符集,而oracle9i则支持57种语言、88个国家地域、235种字符集。由于oracle字符集种类多,且在存储、检索、迁移oracle数据时多个环节与字符集的设置密切相关,因此在实际的应用中,数据库开发和管理人员经常会遇到有关oracle字符集方面的问题。本文通过以下几个方面阐述,对oracle字符集做简要分析

二.字符集基本知识

2.1字符集
    实质就是按照一定的字符编码方案,对一组特定的符号,分别赋予不同数值编码的集合。Oracle数据库最早支持的编码方案是US7ASCII。
    Oracle的字符集命名遵循以下命名规则:
    <Language><bit size><encoding>
    即:  <语言><比特位数><编码>
    比如: ZHS16GBK表示采用GBK编码格式、16位(两个字节)简体中文字符集

2.2字符编码方案
<st1:chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">2.2.1</st1:chsdate> 单字节编码
    (1)单字节7位字符集,可以定义128个字符,最常用的字符集为US7ASCII
    (2)单字节8位字符集,可以定义256个字符,适合于欧洲大部分国家
    例如:WE8ISO8859P1(西欧、8位、ISO标准8859P1编码)
2.2.2 多字节编码
    (1)变长多字节编码
    某些字符用一个字节表示,其它字符用两个或多个字符表示,变长多字节编码常用于对亚洲语言的支持,   例如日语、汉语、印地语等
    例如:AL32UTF8(其中AL代表ALL,指适用于所有语言)、zhs16cgb231280
    (2)定长多字节编码
    每一个字符都使用固定长度字节的编码方案,目前oracle唯一支持的定长多字节编码是AF16UTF16,也是仅用于国家字符集
2.2.3 unicode编码
    Unicode是一个涵盖了目前全世界使用的所有已知字符的单一编码方案,也就是说Unicode为每一个字符提供唯一的编码。UTF-16是unicode的16位编码方式,是一种定长多字节编码,用2个字节表示一个unicode字符,AF16UTF16是UTF-16编码字符集。
    UTF-8是unicode的8位编码方式,是一种变长多字节编码,这种编码可以用1、2、3个字节表示一个unicode字符,AL32UTF8,UTF8、UTFE是UTF-8编码字符集

2.3 字符集超级
    当一种字符集(字符集A)的编码数值包含所有另一种字符集(字符集B)的编码数值,并且两种字符集相同编码数值代表相同的字符时,则字符集A是字符集B的超级,或称字符集B是字符集A的子集。
    Oracle8i和oracle9i官方文档资料中备有子集-超级对照表(subset-superset pairs),例如:WE8ISO8859P1是WE8MSWIN1252的子集。由于US7ASCII是最早的Oracle数据库编码格式,因此有许多字符集是US7ASCII的超集,例如WE8ISO8859P1、ZHS16CGB231280、ZHS16GBK都是US7ASCII的超集。

2.4 数据库字符集(oracle服务器端字符集)
    数据库字符集在创建数据库时指定,在创建后通常不能更改。在创建数据库时,可以指定字符集(CHARACTER SET)和国家字符集(NATIONAL CHARACTER SET)。
<st1:chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">2.4.1</st1:chsdate>字符集
    (1)用来存储CHAR, VARCHAR2, CLOB, LONG等类型数据
    (2)用来标示诸如表名、列名以及PL/SQL变量等
    (3)用来存储SQL和PL/SQL程序单元等
2.4.2国家字符集:
    (1)用以存储NCHAR, NVARCHAR2, NCLOB等类型数据
    (2)国家字符集实质上是为oracle选择的附加字符集,主要作用是为了增强oracle的字符处理能力,因为NCHAR数据类型可以提供对亚洲使用定长多字节编码的支持,而数据库字符集则不能。国家字符集在oracle9i中进行了重新定义,只能在unicode编码中的AF16UTF16和UTF8中选择,默认值是AF16UTF16
2.4.3查询字符集参数
    可以查询以下数据字典或视图查看字符集设置情况
    nls_database_parameters、props$、v$nls_parameters
    查询结果中NLS_CHARACTERSET表示字符集,NLS_NCHAR_CHARACTERSET表示国家字符集
2.4.4修改数据库字符集
    按照上文所说,数据库字符集在创建后原则上不能更改。如果需要修改字符集,通常需要导出数据库数据,重建数据库,再导入数据库数据的方式来转换,或通过ALTER DATABASE CHARACTER SET语句修改字符集,但创建数据库后修改字符集是有限制的,只有新的字符集是当前字符集的超集时才能修改数据库字符集,例如UTF8是US7ASCII的超集,修改数据库字符集可使用ALTER DATABASE CHARACTER SET UTF8。

2.5 客户端字符集(NLS_LANG参数)
<st1:chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on">2.5.1</st1:chsdate>客户端字符集含义
    客户端字符集定义了客户端字符数据的编码方式,任何发自或发往客户端的字符数据均使用客户端定义的字符集编码,客户端可以看作是能与数据库直接连接的各种应用,例如sqlplus,exp/imp等。客户端字符集是通过设置NLS_LANG参数来设定的。
2.5.2 NLS_LANG参数格式
    NLS_LANG=<language>_<territory>.<client character set>
    Language:显示oracle消息,校验,日期命名
    Territory:指定默认日期、数字、货币等格式
    Client character set:指定客户端将使用的字符集
    例如:NLS_LANG=AMERICAN_AMERICA.US7ASCII 
    AMERICAN是语言,AMERICA是地区,US7ASCII是客户端字符集
2.5.3客户端字符集设置方法
     1)UNIX环境
         $NLS_LANG=“simplified chinese”_china.zhs16gbk
         $export NLS_LANG
         编辑oracle用户的profile文件
    2)Windows环境
         编辑注册表
         Regedit.exe---HKEY_LOCAL_MACHINE---SOFTWARE---ORACLE—HOME0
2.5.4 NLS参数查询
    Oracle提供若干NLS参数定制数据库和用户机以适应本地格式,例如有NLS_LANGUAGE,NLS_DATE_FORMAT,NLS_CALENDER等,可以通过查询以下数据字典或v$视图查看。
    NLS_DATABASE_PARAMETERS--显示数据库当前NLS参数取值,包括数据库字符集取值
    NLS_SESSION_PARAMETERS--显示由NLS_LANG 设置的参数,或经过alter session 改变后的参数值(不包括由NLS_LANG 设置的客户端字符集)
    NLS_INSTANCE_PARAMETE--显示由参数文件init<SID>.ora 定义的参数V$NLS_PARAMETERS--显示数据库当前NLS参数取值
2.5.5修改NLS参数
    使用下列方法可以修改NLS参数
    (1)修改实例启动时使用的初始化参数文件
    (2)修改环境变量NLS_LANG
    (3)使用ALTER SESSION语句,在oracle会话中修改
    (4)使用某些SQL函数
    NLS作用优先级别:Sql function>alter session>环境变量或注册表>参数文件>数据库默认参数

三.导入/导出与字符集转换

3.1 EXP/IMP
    Export 和 Import 是一对读写Oracle数据的工具。Export 将 Oracle 数据库中的数据输出到操作系统文件中, Import 把这些文件中的数据读到Oracle 数据库中,由于使用exp/imp进行数据迁移时,数据从源数据库到目标数据库的过程中有四个环节涉及到字符集,如果这四个环节的字符集不一致,将会发生字符集转换。

EXP
     ____________   _________________  _____________
     |imp导入文件|<-|环境变量NLS_LANG|<-|数据库字符集|
     ------------   -----------------  -------------

IMP
     ____________   _________________  _____________
     |imp导入文件|->|环境变量NLS_LANG|->|数据库字符集|
     ------------   -----------------  -------------

   四个字符集是
   (1)源数据库字符集
   (2)Export过程中用户会话字符集(通过NLS_LANG设定)
   (3)Import过程中用户会话字符集(通过NLS_LANG设定)
   (4)目标数据库字符集

3.2导出的转换过程
    在Export过程中,如果源数据库字符集与Export用户会话字符集不一致,会发生字符集转换,并在导出文件的头部几个字节中存储Export用户会话字符集的ID号。在这个转换过程中可能发生数据的丢失。
例:如果源数据库使用ZHS16GBK,而Export用户会话字符集使用US7ASCII,由于ZHS16GBK是16位字符集,而US7ASCII是7位字符集,这个转换过程中,中文字符在US7ASCII中不能够找到对等的字符,所以所有中文字符都会丢失而变成“?? ”形式,这样转换后生成的Dmp文件已经发生了数据丢失。
因此如果想正确导出源数据库数据,则Export过程中用户会话字符集应等于源数据库字符集或是源数据库字符集的超集

3.3导入的转换过程
    (1)确定导出数据库字符集环境
    通过读取导出文件头,可以获得导出文件的字符集设置
    (2)确定导入session的字符集,即导入Session使用的NLS_LANG环境变量
    (3)IMP读取导出文件
    读取导出文件字符集ID,和导入进程的NLS_LANG进行比较
    (4)如果导出文件字符集和导入Session字符集相同,那么在这一步骤内就不需要转换,如果不同,就需要把数据转换为导入Session使用的字符集。可以看出,导入数据到数据库过程中发生两次字符集转换
    第一次:导入文件字符集与导入Session使用的字符集之间的转换,如果这个转换过程不能正确完成,Import向目标数据库的导入过程也就不能完成。
    第二次:导入Session字符集与数据库字符集之间的转换。
    然而,oracle8i的这种转换只能在单字节字符集之间进行,oracle8i导入Session不支持多字节字符集之间的转换,因此为了避免第一次转换,导入Session使用的NLS_LANG与导出文件字符集相同,第二次转换(通过SQL*Net)支持任何两种字符集。以上情况在Oracle9i中略有不同

四.乱码问题

    oracle在数据存储、迁移过程中经常发生字符乱码问题,归根到底是由于字符集使用不当引起。下面以使用客户端sqlplus向数据库插入数据和导入/导出(EXP/IMP)过程为例,说明乱码产生的原因。

4.1使用客户端sqlplus向数据库存储数据
    这个过程存在3个字符集设置
    (1)客户端应用字符集
    (2)客户端NLS_LANG参数设置
    (3)服务器端数据库字符集(Character Set)设置
    客户端应用sqlplus中能够显示什么样的字符取决于客户端操作系统语言环境(客户端应用字符集),但在应用中录入这些字符后,这些字符能否在数据库中正常存储,还与另外两个字符集设置紧密相关,其中客户端NLS_LANG参数主要用于字符数据传输过程中的转换判断。常见的乱码大致有两种情形:
    (1)汉字变成问号“?”;
当从字符集A 转换成字符集B时,如果转换字符之间不存在对应关系,NLS_LANG使用替代字符“?”替代无法映射的字符
    (2)汉字变成未知字符(虽然有些是汉字,但与原字符含义不同)
转换存在对应关系,但字符集A 中的字符编码与字符集B 中的字符编码代表不同含义

4.2发生乱码原因
    乱码产生是由于几个字符集之间转换不匹配造成,分以下几种情况:
    (注:字符集之间如果不存在子集、超集对应关系时的情况不予考虑,因为这种情况下字符集之间转换必产生乱码)  
    1)服务器端数据库字符集与客户端应用字符集相同,与客户端NLS_LANG参数设置不同
    如果客户端NLS_LANG字符集是其它两种字符集的子集,转换过程将出现乱码。
    解决方法:将三种字符集设置成同一字符集,或NLS_LANG字符集是其它两种字符集的超集
    2)服务器端数据库字符集与客户端NLS_LANG参数设置相同,与客户端应用字符集不同
    如果客户端应用字符集是其它两种字符集的超集时,转换过程将出现乱码,但对于单字节编码存储中文问题,可参看本文第5章节的分析
    3)客户端应用字符集、客户端NLS_LANG参数设置、服务器端数据库字符集互不相同
    此种情况较为复杂,但三种字符集之间只要有不能转换的字符,则必产生乱码

4.3导入/导出过程出现乱码原因
    这个过程存在4个字符集设置,在3.1章节中已分析
   (1)源数据库字符集
   (2)EXP过程中NLS_LANG参数
   (3)IMP过程中NLS_LANG参数
   (4)目标数据库字符集
    出现乱码原因
    1)当源数据库字符集不等于EXP过程中NLS_LANG参数,且源数据库字符集是EXP过程中NLS_LANG的子集,才能保证导出文件正确,其他情况则导出文件字符乱码
    2)EXP过程中NLS_LANG字符集不等于IMP过程中NLS_LANG字符集,且EXP过程中NLS_LANG字符集是IMP过程中NLS_LANG字符集的子级, 才能保证第一次转换正常,否则第一次转换中出现乱码。
    3)如果第一次转换正常,IMP过程中NLS_LANG字符集是目标数据库字符集的子集或相同,才能保证第二次转换正常,否则则第二次转换中出现乱码

五.单字节编码存储中文问题

    由于历史的原因,早期的oracle没有中文字符集(如oracle6、oracle7、oracle7.1),但有的用户从那时起就使用数据库了,并用US7ASCII字符集存储了中文,或是有的用户在创建数据库时,不考虑清楚,随意选择一个默认的字符集,如WE8ISO8859P1或US7ASCII,而这两个字符集都没有汉字编码,虽然有些时候选用这种字符集好象也能正常使用,但用这种字符集存储汉字信息从原则上说就是错误的,它会给数据库的使用与维护带来一系列的麻烦。
    正常情况下,要将汉字存入数据库,数据库字符集必须支持中文,而将数据库字符集设置为US7ASCII等单字节字符集是不合适的。US7ASCII字符集只定义了128个符号,并不支持汉字。另外,如果在SQL*PLUS中能够输入中文,操作系统缺省应该是支持中文的,但如果在NLS_LANG中的字符集设置为US7ASCII,显然也是不正确的,它没有反映客户端的实际情况。但在实际应用中汉字显示却是正确的,这主要是因为Oracle检查数据库与客户端的字符集设置是同样的,那么数据在客户与数据库之间的存取过程中将不发生任何转换,但是这实际上导致了数据库标识的字符集与实际存入的内容是不相符的。而在SELECT的过程中,Oracle同样检查发现数据库与客户端的字符集设置是相同的,所以它也将存入的内容原封不动地传送到客户端,而客户端操作系统识别出这是汉字编码所以能够正确显示。
    在这个例子中,数据库与客户端都没有设置成中文字符集,但却能正常显示中文,从应用的角度看好象没问题。然而这里面却存在着极大的隐患,比如在应用length或substr等字符串函数时,就可能得到意外的结果。
    对于早期使用US7ASCII字符集数据库的数据迁移到oracle8i/9i中(使用zhs16gbk),由于原始数据已经按照US7ASCII格式存储,对于这种情况,可以通过使用Oracle8i的导出工具,设置导出字符集为US7ASCII,导出后使用UltraEdit等工具打开dmp文件,修改第二、三字符,修改 0001 为0354,这样就可以将US7ASCII字符集的数据正确导入到ZHS16GBK的数据库中。

六.结束语

    为了避免在数据库迁移过程中由于字符集不同导致的数据损失,oracle提供了字符集扫描工具(character set scanner),通过这个工具我们可以测试在数据迁移过程中由于字符集转换可能带来的问题,然后根据测试结果,确定数据迁移过程中最佳字符集解决方案。

编写大容量和健壮的服务器系列—处理IOCP连接关闭

发布者:罗堃,发布时间:2009-2-19 下午8:13

及时监测连接被动关闭

       除非有特别要求,否则你应该总是对每个连接保持一个挂起的接收 pending io

(使用 WSARecv 投递)。如果用户主动关闭连接,你的 GetQueuedCompletionStatus 调用将返回成功,但接收到的数据长度为 0 ,你能根据这点检测连接是否已被对方关闭。如果连接被重置或者 io 被取消(如果你调用了 CancelIo 的话), GetQueuedCompletionStatus 将返回失败,注意这时还应该判断 GetQueuedCompletionStatus 调用返回的 lpOverlapped 值,如果该值不为 NULL ,说明 iocp 已经检测到一个连接已经中断。

 

安全的关闭连接

       很多人写的服务器网络库有一个难以接受的缺陷(包括我曾就职公司的一些同事),当服务器程序主动关闭连接时,刚发往客户端的包有时出现丢失,这时他们推荐的方式往往是发送数据后等待几秒再关闭连接。豪无疑问,这是一种笨拙的实现方式,他们遇到的问题根源是什么呢?

       在非 IOCP 模式网络程序中,你只要简单的调用 closesocket 函数就可以确保数据在操作系统释放 socket 之前安全到达对方,但在 IOCP 模式下,如果调用 closesocket 时有未决的 pending IO 将导致 socket 被重置,所以有时会出现数据丢失。正统的解决方式是使用 shutdown 函数(指定 SD_SEND 标志),注意这时可能有未完成的发送 pengding IO ,所以你应该监测是否该连接的所有是否已完成(也许你要用一个计数器来跟踪这些 pending IO ),仅在所有 send pending IO 完成后调用 shutdown

当你调用 shutdown 时,也许数据仍然停留在操作系统的缓冲,操作系统将在数据发送完后发出一个 FIN 包来启动关闭进程,客户端接收完数据后,将接受到一个 0 长度的包,以此判断连接已关闭(你写的客户端肯定有检测连接关闭,不是吗?),然后调用 closesocket ,这时服务器的 GetQueuedCompletionStatus 将接收到一个数据长度为 0 的包,这时你就可以调用 closesocket ,并释放相关连接资源。

在绝大部分情况下上述的过程连接能完美的关闭。如果你特别注重服务器的安全性和健壮性,可能你还需要做一个“连接关闭队列”,对每个已调用 shutdown 的连接放到这个队列,然后定时的对这个队列扫描,如果一个连接 5 秒(你也可以自己调整)还不能关闭,那么就强制关闭它。

 

处理大并发短连接时如何避免 TIME_WAIT 状态

       关于如何避免 TIME_WAIT 这个问题,一直没看到有效的处理方式(至少我没有), 我将在这里披露一种有效的方式。回到上一段,我们最后调用了 closesocket 关闭连接,这时仍然可能出现 TIME_WAIT 状态,但注意这时所有的数据都已经传输完毕,因此你可以强制关闭 socket 避免服务器连接进入 TIME_WAIT (这时只会发出连接重置 RESET 包)

// 立即关闭 ( 避免出现 TIME_WAIT 状态 )

              LINGER linger = {1,0};

              setsockopt(socket, SOL_SOCKET, SO_LINGER,

                     (char *)&linger, sizeof(linger));

 

 

socket唯一性问题

正常情况下 SOCKET 套结字值是唯一的,但是操作系统在分配 socket 值时有随机性,最近关闭的 socket 值可能重新分派给一个刚刚建立的新的 socket. ,尤其在大并发短连接的情况下。一个健壮的服务器 IOCP 网络库必须要考虑 socket 唯一性的问题,由于 IOCP 的排队机制,意味着当你调用 closesocket 关闭 socket 后, IOCP 队列中可能仍然堆积了该 socket 的一些 I/O completion packet ,而此时,刚关闭的 socket 值又分派给一个刚刚建立的 socket ,所以,你必须对 GetQueuedCompletionStatus 获取到的 I/O completion packet 小心翼翼处理,避免出现数据混乱,然而,最好的方式等到所有 I/O completion packet 返回后才调用 closesocket 关闭该 socket

IVR平台基础知识——新手入门篇

发布者:罗堃,发布时间:2009-1-21 上午7:04

一、        如何来理解IVR平台?

很多人认为,IVR平台和短信平台应该差不多。其实,这是不正确的,两者有着非常大的区别,主要的区别表现在以下几个方面:

n        设备区别:短信平台一般不需要投入新的设备,利用一台电脑服务器甚至普通PC即可,而IVR平台则需要投入与普通电脑服务器或PC不同的设备,如语音中继卡等等;
n        接入区别:短信平台通过简单的INTERNET或DDN与运营商的短信中心进行连接,而IVR平台则需要通过语音中继与运营商相连接。
n        应用区别:短信平台的应用程序(产品)一般都是常用的编程语言写成,可直接运行于PC操作系统中,而IVR程序一般都用IVR平台厂商提供的独特编程语音写成,无法直接运行于PC中。
n        ……

那么,我们该如何来理解IVR平台呢?
打个不怎么恰当的比方,一台能上网的个人PC由以下几部份组成:电脑硬件(显示器、硬盘、内存等)、操作系统(DOS、WINDOWS、UNIX等)、应用程序(浏览器、WINAMP、QQ、MSN等)、网线(ADSL、宽带等),只有配全这些,用户才能够上网浏览信息或同其它用户交流。

其实,IVR平台也可以这么通俗地进行对比理解:
n        IVR平台专用设备(语音中继卡、会议卡、座席卡、信令卡或信令网关等等)--对应于个人PC的电脑硬件;
n        IVR平台软件系统(新太平台、青牛平台、高阳平台等)--对应于个人PC的操作系统;
n        IVR流程(电话QQ、语音点歌等)--对应于个人PC的应用程序;
n        中继连接--对应于个人PC的网线连接。
这样一来,是不是更容易理解了呢?

当然,这样的比方还不是完全恰当的,比如说,IVR平台的设备也需要用到个人电脑或服务器常用的硬盘、显示器等,IVR平台也需要WINDOWS等操作系统支持等。
二、    IVR平台与IVR设备厂商
很多人误以为,每个提供IVR平台的厂商都有自己的设备。其实不然,对于大部分平台厂商来说(包括较大的新太、青牛、高阳等),其设备都不是自己的,而是从设备厂商处采购的。
因此,与SP相关的IVR厂商应分为IVR平台厂商(软件集成厂商)和设备厂商等,而直接与SP打交道的一般是IVR平台厂商,他们除了提供IVR软件平台外,还负责为SP设计平台架构(设备配置方案)并做系统集成等工作,平台的维护工作也是由他们来完成。
三、    三、        IVR平台的相关名词解释

中继线:连接用户交换机、集团电话(含具有交换功能的电话连接器)、无线寻呼台、移动电话交换机等与市话交换机的电话线路称为中继线。
一般来说,中继线的计量单位以"E1(读成英文发音ONE)"来计算,一个E1为32线,即32条话路,在IVR平台上,其中2路一般用来做业务处理,因此,一个E1的有效线路为30线。

中继卡:平台与中继线进行连接的资源分配与处理卡。

中继资源:在IVR平台中,用户拨入IVR平台或者IVR平台外呼用户,均要占用1条通话线路,即中继资源。

语音卡:又叫电脑与电话语音处理卡,平台上用来播放语音的设备。一般来说语音卡也是以线路来计算的,并且大多是30的倍数。

语音资源:语音卡上的线路资源。比如,播放一条录音必须占用一线语音资源。

会议卡:用来进行多方通话的设备,电话会议、电话QQ的多人聊天室等业务必须有会议卡的支持。会议资源也是以线路数来进行计算的。

会议资源:会议卡上的线路资源,比如一个8人聊天室就需要占用8个会议资源。

信令:信令是通信系统中不同设备之间交换的信息,控制通信设备动作的信号。信令方式是有关信令传递和处理的协议和规范。 信令传递方式:要遵守一定的约束、协议和规范。
IVR平台必须以规定的信令方式接入通信网,在国内,比较常用的信令有No.1(1号信令)、No.7(7号信令)、ISDN等。

信令卡或信令网关:如果IVR平台采用No.7(7号信令)接入通信网,则需要信令卡或信令网关来支持。

TTS: TTS是Text To Speech的缩写,即"从文本到语音"、文本转语音等。通过TTS,IVR平台可顺利将文字转换为语音,播放给用户收听。TTS按路来计算,如10路TTS等。

ASR:Automatic Speech Recognition,自动语音识别。语音识别技术现在在IVR应用中越来越广泛,如"语音电话本"、"语音点歌"等业务则是ASR的典型应用,是最有前景的应用之一。
四、    四、        如何来计算IVR平台的容量?

很多比较外行的SP在和平台厂商联络时,经常说:"给我一份平台报价。"其实,这种说法很让平台厂商为难。为什么呢?平台的报价设计到平台的容量和配置,平台容量不同、配置不同,报价自然就不一样。

那么,如何来计算IVR平台的容量呢?

一般来说,平台的容量是以线数来计算的,并且一般是30的倍数--和中继数有关(一个E1的线数是30线)。因此,平台的容量一般是30线、60线、120线、240线、480线、960线等等,指的就是中继接入线数。如容量为30线的IVR平台就是指平台允许30个用户同时拨入。
五、    五、        IVR平台主要由哪些设备构成?
一套完整的IVR平台,主要由以下设备构成:
工控机(PCI结构):相当于个人PC的主机,上面有CPU、板卡插槽、底板等,其它板卡就插在工控机上。
机框(CPCI结构):和工控机的作用类似,不过更复杂更贵,CPU、插槽、内存、硬盘、键盘鼠标都包括了。
语音卡和中继卡:现在的大多数设备,已将语音卡和中继卡集成在一块卡上,叫语音中继卡或中继语音卡。
会议卡:大部分厂商的会议卡是独立的,也有部分厂商的某些语音中继卡上还集成了会议卡。
座席卡:进行人工服务需要用到的资源卡。如电话QQ里的主持人陪聊、呼叫中心的座席服务等,想对应的是,通过一台电脑或话机接入座席卡,即可进入IVR平台和用户对话或进入聊天室主持聊天。
7号信令卡或7号信令网关:如果运营商要求IVR平台用7号信令接入通信网,则IVR平台需要7号信令卡或7号信令网关支持。这东西可不便宜哦,价格起码在4万以上。
应用服务器:用来存放相关业务数据、流程、语音等。普通电脑服务器即可。
数据库软件:IVR业务流程、计费、日志等都需要数据库来支持。
TTS:如果你的业务需要用到将文字转换为语音。
ASR:如果你的业务需要用到语音识别技术。
……
在IVR平台中,设备的核心就是板卡(语音中继卡等)。
六、    六、        常见的IVR平台架构(配置方案)

常见的IVR配置方案主要有以下三种:

PCI结构:PCI即外围设备互联之意,1992年由Intel发布。
CPCI结构:CompactPCI简称cPCI,中文又称紧凑型PCI,是国际PICMG协会提出来的一种总线接口标准。
Excel媒体交换机结构: Excel于2003年推出一种新的DSP2技术,内置强大的媒体处理能力,有效提高了中继资源的利用率并避免了多点故障。

在这三种方案中,PCI结构的优点是价格相对比较便宜,但稳定性稍差,扩容能力不强,单机一般不能超过240线,最大难以超过480线。CPCI方案在小容量时价格相对较贵一点,中大容量则价格较为划算,但稳定性较好,可扩容能力强。Excel媒体交换机方案的特点和CPCI方案较为接近,但呼叫处理能力更强,更适合于电信级的应用,从价格上来比较,在中小规模时可能比CPCI方案更贵一点。

因此,对于SP来说,如果从成本上来考虑,并且业务规模不是特别大的话,一般考虑采用PCI结构,而预计业务较为有前景,可能需要大规模扩容时,则应该选用CPCI结构。
七、    七、        主要的IVR板卡厂商

在国内,不管是PCI结构还是CPCI结构,IVR的板卡厂商都分为国外厂商和国内厂商两部分。

国外厂商中,INTEL和NMS等厂商的设备被用得比较多,目前固网的168和116平台、大部分省市的移动第三方平台,以及很多大SP自带的平台,大部分用的是他们的板卡设备。他们产品的主要优点是板卡使用寿命长,性能较为稳定,但价格非常昂贵。

国内厂商中,比较知名的有东进、杭州三汇、深圳鼎铭等。值得一提的是,近年来,东进的产品的性能已较为接近国外厂商的产品,但由于价格要便宜很多,因此,给国外厂商造成了很大的威胁,许多SP已开始采用东进的产品。

“IVR”小常识(新人必读)

发布者:罗堃,发布时间:2009-1-21 上午7:00

"IVR"小常识
    1、什么是IVR?
    IVR(Interactive Voice Response),即互动式语音应答,是基于手机的无线语音增值业务的统称。手机用户只要拨打指定号码,就可根据操作提示收听、点送所需语音信息或者参与聊天、交友等互动式服务。

    2、什么是"语音杂志"?
    语音杂志业务以话音内容服务为核心,向中国移动手机用户提供一个集语音和短信于一体的语音信息服务获取平台。用户通过拨打语音接入号码,可以为自己或他人获得语音信息、短消息内容服务。为您提供最新、最实用、最全面的信息内容,无论是资讯类还是娱乐类产品都紧贴您的心思,眼见为虚,耳听为实!

    3、如何使用IVR业务?
    无须注册,不用更改手机设置,中国移动手机用户只要拨提供的IVR业务号码即可随时随地使用我们的IVR业务。

    4、如何获得IVR业务的资费标准?
    用户拨打任何IVR业务号码时,都会首先听到该服务的信息费、基本通话费的资费标准详细说明。资费标准说明部分不收费。

    5、IVR业务是如何付费的?
    由中国移动每月从用户手机话费中收取。 !



IVR(Interactive Voice Response)即交互式语音应答,可以提高呼叫服务的质量并节省费用。随着技术的进步,IVR也有了一系列的发展,可以根据用户输入的内容播放有关的信息,可以是操作提示也可以是具体的信息内容。
--在Internet电话中使用的IVR经历了从集中到分布的过程,分布式IVR采用加入智能网关的功能,使得原本远程发送的语音提示信息现在只需要在本地发送,从而既减少了开销又提高了服务质量。
--一、IVR技术概述
--1.为什么要使用IVR
--使用IVR可以使用户一天24小时随时都能得到信息服务,提高服务质量,以及协调用户操作过程。如果在呼叫中心装入IVR系统,大部分呼叫实现了自动化,可以节省原来60%的费用,同时还能减轻座席代理人的负担,使之仅处理确实需要人工处理的呼叫。而且IVR系统使得用户可以随时随地进行访问,因此得到了用户的普遍认可。目前,很多企业正致力于使用IVR的这一优点来吸引用户。
--2.开放而充满竞争的市场
--1999年3月的计算机电话博览会上,微软与Dialogic公司宣布了他们之间达成的一项协议:微软将为Dialogic公司的CT Media产品颁发许可证,Dialogic为微软提供升级服务,同时微软把CT Media集成到Windows NT中。这就意味着不久的将来,IVR软件的销售和使用价格会很低廉。
--微软和Dialogic在计算机电话博览会上的联合声明将使得整个计算机电话领域发生深刻的变化。
--这一协议综合了企业计算机电话论坛(ECTF,专为计算机电话技术提供开放竞争市场的组织)的标准,并已考虑到了TAPI 3.0和S.100(ECTF的标准接口)。当CTMedia集成到Windows NT后,整个环境就会发生变化,IVR零售商所要做的仅仅是开发出高水准的应用程序即可。
--建立呼叫中心可以独立地购买操作平台,然后再购买ACD、IVR或语音邮件应用软件,再把它放到服务器上,使之共享端口,集成在一起。因为所有这些部件都不冲突,CT Media和Windows NT可以让它们一起即插即用地使用。在Windows NT和CT Media的协助下,计算机电话的软件和硬件很容易协调起来,而且硬件具有完全的独立性。这就构成了开放的电话系统。
--越来越多的电话呼叫实现了自动化处理,各公司也在不断地扩大IVR的自动化功能,以不断为用户提供良好的服务。IVR是所有与用户连接的关键部分,所以他们希望IVR不仅能处理重复性的工作,而且还有助于提高服务质量和增加利润。随着呼叫中心的改进,IVR的角色也在不断地被重新定义。
--3.IVR的一些变化
--IV系的一些变化表现在如下几方面:
--多通道自服务平台。单通道IVR系统工扩展成支持IVR、语音识别和电子邮件的完全的多通道自服务平台。
--有目标的信息服务。自服务平台可用于一对一方式的市场营销和广告促销,这一平台可以有目标地向特定用户发送信息,如用户登录到银行的网站申请商业贷款,商家获得这一信息并将它实时记录下来为以后的营销做准备,当下次他(她)呼入IVR系统时,商家就能根据所掌握的用户信息提供合适的促销材料。
--设备优良。用户不希望被硬件系统所束缚,不希望追随硬件升级而不断投入资金。因此,公司希望从一流的软件商那里买服务软件并从一流的硬件商那里买硬件。
--语音识别。语音识别是另一个影响深远的大趋势,估计在两年内30%的IVR应用系统将会装入语音识别功能。
--按键信号音以别和语音识别之间的差异很明显。语音识别所用的花费是按键信号音识别的两倍,但它却能使原本很复杂的应用系统变得很简单,甚至还有一些系统不能用按键信号音识别做,如航班定票系统所要处理的信息是起飞城市和目的城市,这一系统如果不用语音识别就很难
自动实现。
--目前,在美国已有一个在全国拥有6000个零售商的器械公司,通过IVR系统,使用语音识别定货并返回定货信息的结果。4000种不同的器械都用唯一的15位长的,包括字母与数字的模型号表示,因而语音识别的正确率很高,目前正确率高达96%。现在的银行系统用户只需按电话上的按键就可以实现支付帐单操作,但按键操作最终会被直接用语音操作所取代。
--4.几个典型的IVR系统
--下面介绍国外的一些具体的产品。
--Brite语音系统中的IVR/CTI系统与其它系统的差异就在于它的灵活性。由于它基于客户机/服务器机制,因而中央计算机系统可以扩充成包含足够的语音处理线路,以满足向成千上万的同时发出呼叫的用户提供特定用户信息的需求。事实上,这一系统与主要的制造商的产品配合得很好。一些Brite IVR与计算机电话系统结合,产生了更有价值的系统,包括终端屏幕弹出用户信息。如果呼叫者已经通过电话输入了一些信息,代理人员就可以不再要求其重新输入信息了。
--Inter Voice平台提供单点接入业务,还对大量宿主、数据库、网络和电话接口进行控制,与所有电话、数据库和信息系统融合成一个整体。系统为用户访问设备和数据库接口提供了很多的途径。使用该系统的宿主和数据库处理接口,用户不必为宿主计算机或数据库服务器编程就可以透明地访问信息。操作平台除了应答呼叫和为呼叫寻找路由以外,平台还为授权用户提供如厂业务:通过电话(按键信号音识别或说话)或浏览器访问数据库的业务;文语转换功能;传真回应核实与信息填充;允许呼叫者简短留言的语音表格;基于时间日期及数据触发的日常商务自动处理。
--由于建立在Intuity Conversant平台上,Lucent技术的CentreVu语音响应解决方案使呼叫中心拥有一系列的IVR功能。最新开发的系统是一套自然语言以别系统,包括呼叫仿真、单词学习工具和语言识别的引擎选择,Lucent在这套系统中提供12种不同的语言和方言的识别。文语转换方面新的进展已使系统可以用英语、西班牙语和法语向呼叫者发送语音提示。CentreVu语音响应方案还包括一个用于实现个人化服务的辅助系统。
--Whitecap开发的Whitecap IVR是可升级的语音处理平台,系统允许用户快速开发和执行适合自己的IVR应用程序。当用户业务改变时、可以让呼叫流程和数据库的登录也跟着改变。如果用户需要座席代理人员参与,Whitecap的路由器和计算机电话就能根据过去通话的情况把呼叫或Web访问传送给最佳的座席代理。
--Whitecap Visual Script Buider允许创建和修改Script,还允许定义呼叫处理流程。如加入其他的Whitecap系统组件,还能为智能路径和Internet回呼创建呼叫控制流程。Whitecap呼叫管理收集用户定义的所有信息,在整个通话过程中,这些信息一直被追踪,这样从IVR发出的用户信息就能与呼叫一起发送。
--二、IVR改进了Internet电话
--1.集中式IVR的代价与风险
--在提供国际电话卡服务时,大部分邮递节点不在每个国家都物理上存在,而是在逻辑上进行维护,并已从外部的邮递节点租用带宽。然而用现有的IP电话,所有计费功能、主叫识别以及与电话卡相关的用户提示信息都必须集中处理,这是由于网关缺乏像集中电话网中提供的IVR和计费功能之类的智能特性。在集中式的结构下,国际电话呼叫从"请输入电话卡号和PIN号"开始到"输入要呼叫的号码",直到接通电话为止,每处理一个国际电话卡业务至少三次访问中央数据库系统。每个用户的提示信息必需从中央IVR系统发出,并且每次用户响应也得送回IVR系统。
--IP电话的开办是为了节省费用,但在网络上往返式地传送提示信息造成了大量的开销。如果传递节点租用带宽,那么它们不仅为语音传输付给带宽提供者费用,还要为传输语音提示信息所造成的开销付费。而且语音提示信息还会拥塞网络,为了保证语音质量,就得提供更多的带宽。在实际应用中,看似很少的语音提示开销能聚集成很大的损失。
--但是,有一种方法可以避免这一问题,即把传统语音世界的功能和特性分布到基于IP的网络中去。
--2.把提示语音分散到各地,以节省开销为了节省提供语音提示而产生的往返传送开销,传递节点把IVR的中央控制功能分散到各个节点。在分布式的体系结构下,通过把IVR功能集成到IP电话网关,要求输入用户电话卡号之类的语音提示就可以实现本地处理。
--用一种简单的控制语言配置网关,使它具有发出语音提示、电话卡核实和拨号处理功能。这样,只有远距离的长途语音信息以语音的方式在IP网络上传送。在此之前,像"请输入卡号"这样的提示信息都由中央控制,因而它们都必须以实际的语音传送。有了集成化的IVR后,中央控制部分把IP信息发送给局域网网关,局域网网关的IVR根据中央控制部分IP信息所表示的指令发送给用户语音提示信息。因为语音提示信息都保留在本地,因而不需要在广域网上传输。广域网上需要传送的只是很小的用于告诉局域网网关该发什么提示信息的IP包,这样节省了大量的带宽资源。
--把功能分布到网关上会有一定的开销,但能带来更大的收益。
--(1)低代价。分布式IVR显然能节省大笔的开销,首先它消除了迂回路由,正是迂回路由使得带宽费用居高不下。如传递节点建在美国,而持卡人在意大利打电话到阿根廷,那么该呼叫必须先返回到美国,每次传递节点都得为自己的节点上的远距离流量传送付费。
--在IVR使用之前,传递节点只能通过在每个国家放置交换设备实现传送功能。可以想象,这样做代价多么昂贵。有了IVR后这些选择都可以在本地做,因而可以充分利用最小代价路径(LCR)。而且,分布式IVR可以预防欺骗行为发生。用集中式IVR,假冒的呼叫者可以在他的PIN生效之前使用传递节点的带宽资源,分布式IVR能保证只有缴费用户才能使用珍贵的带宽资源。由于传递节点刚开始不需要多少带宽,也不需要频繁扩容,因此节省了费用。
--同时,还有一种"软节省"。一方面,每个节点不需要独享资源,节省了投资。另一方面,由于呼叫路由具有很强的灵活性,传递节点可以充分利用本地呼叫路由计划和最小代价路径(LCR)计划减少开销。呼叫可以很快而且以较小的代价完成。
--(2)较高的语音质量。可以不必经过中央处理,直接在本地控制呼叫,在节省费用的同时也提高了语音质量。在两个节点之间采用最短路由传输,减少话务包的压缩和解压的次数,这样使得呼叫快速传输,从而保证了每个呼叫的完整性。
--(3)整个网络的可*性提高。把IVR加入到IP电话网关是一种提高整个网络可*性的策略,与基于PC的网关和路由器不同,真正传递节点的网关从根本上是按照可*性标准设计的。这样的网关采用双机平行处理结构,保证数据是冗余的,当其中一个不能正常工作时,网关也能正常运转。
--3.IVR十IP=MVP
--从某种意义上来说,IVR是呼叫处理时用户服务的代表,其作用不容忽视。
--随着Intemet电话基础结构的不断发展,高级网关和网络的分布智能化也不断使得服务质量提高。Internet电话最让人欣慰的现象是它得到了广泛的支持,因为大量的最终用户对Internet电话的价廉和方便还有进一步要求,深信IVR仅仅是Internet电话发展史上的里程碑之一。
--IVR技术和Internet的结合将提供更加良好的服务,相关的技术有待于进一步的研究。

联通彩e接口开发

发布者:罗堃,发布时间:2009-1-18 下午6:03   [ 更新时间:2009-1-18 下午6:04 ]

前段时间开发联通彩e接口,期间遇到很多问题,在朋友的帮助和自己的摸索中总算完成了接口的开发。 在sp联盟论坛上也见到许多同行各种各样的问题,因此将开发细节整理成文,希望能给与我当初一样 困扰的人以帮助。 第一次接触彩e,感觉有点无从下手,接口指南几百页之多,我在开发的时候不知道联通提供测试的接入 平台以供调试,而是对着接口规范编写代码,然后模拟接口规则生成数据,这些都是在单元测试中完成 的,到接入uni-wise测试环境时,问题多多。

概述
本 文以java语言为例,讲述彩e接口开发的点滴。参考的接口指南为《中国联通增值业务综合管理及接入平台SP接口规范v1.2》, 文中代码均经过测试,且与uni-wise平台能正常运行。彩e与sp接口包括:sso接口、预定接口、取消接口、彩e push接口,取 消push接口,查询push接口、wap push接口。文中除了wap push接口,其余的将会一一介绍。 彩e接口的开发其实就是sp与联通uni-wise平台之间的通信,uni-wise平台是以web方式工作,因此与sp的交互大部分通 过http+xml协议传输。 笔者在开发的过程中也曾用C#写过彩e的部分接口代码,如有此需求,我也将整理成文。

SSO 是 Single sign on的缩写,即单点登录,彩e接口中实现的功能是,用户在uni-wise平台或sp平台只需登录一次,即可 访问相关资源。通过cookies机制实现。
1.1. 传输安全
出于安全考虑,网络的传输中经常对传输数据做加密和编码处理,彩e接口开发中的一个关键点也是对加密解密的代码编写。 其中涉及以下几种:

1、md5加密,该加密算法是单向加密,即加密的数据不能再通过解密还原。相关类包含在java.security.MessageDigest包中。

2、3-DES加密,该加密算法是可逆的,解密方可以通过与加密方约定的密钥匙进行解密。相关类包含在javax.crypto.*包中。

3、base64编码,是用于传输8bit字节代码最常用的编码方式。相关类在sun.misc.BASE64Decoder 和sun.misc.BASE64Encoder 中。

4、URLEncoder编码,是一种字符编码,保证被传送的参数由遵循规范的文本组成。相关类在java.net.URLEncoder包中。

1.2. 生成请求票根

当 用户从SP平台向uni-wise发起登录请求时,SP平台需要生成一个合法的票根,以http协议传输给uni-wise平台。 生成请求票根的规则是:SPTicketRequestValue = URLEncoding{UNICODE(SPCode +“$”)+ Base64 [Encrypt (UNICODE(Seed + “$”)+ Digest)]}

1、生成Seed: returnUrl + "$" + timeStamp;returnUrl为登录成功后接收uni-wise的响应票根链接。timeStamp为生成的 时间戳。

例 1.1. TimeStamp实现代码

public String getTimeStamp()
{
Calendar cal=Calendar.getInstance();
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
String timeStamp=formatter.format(cal.getTime());
return timeStamp;
}

2、生成Digest :Base64{Hash[UNICODE(SPCode +"$"+ Seed + "$" + SPKey)]},其中Hash算法采用md5

例 1.2. Digest的实现代码

public String getDigest(String strSrc)
{
//String strSrc = spCode + "$" + getSeed() + "$" + spKey;
BASE64Encoder base64en = new BASE64Encoder();
String digest="";
try
{
byte[] srcMD5 = md5Encrypt(strSrc);
digest = base64en.encode(srcMD5); (1)
}
catch(Exception e){
e.printStackTrace();
}
return digest;
}

private byte[] md5Encrypt(String strSrc)
{
byte[] returnByte = null;
try
{
MessageDigest md5 = MessageDigest.getInstance("MD5"); (2)
returnByte = md5.digest(strSrc.getBytes("GBK"));
}
catch(Exception e)
{
e.printStackTrace();
}
return returnByte;
}

(1) 用base64编码

(2) 指定加密方式为md5

3、生成密钥匙,用联通提供的key,进行md5加密。

例 1.3. 得到3-DES的密钥匙

private byte[] getEnKey(String spKey)
{
byte[] desKey=null;
try
{
byte[] desKey1 = md5Encrypt(spKey);
desKey = new byte[24];
int i = 0;
while (i < desKey1.length && i < 24) {
desKey[i] = desKey1[i];
i++;
}
if (i < 24) { (1)
desKey[i] = 0;
i++;
}
}
catch(Exception e){
e.printStackTrace();
}
return desKey;
}

(1) 根据接口规范,密钥匙为24个字节,md5加密出来的是16个字节,因此后面补8个字节的0


4、 生成SPTicketRequestValue,URLEncoding{UNICODE(SPCode +“$”)+ Base64 [Encrypt (UNICODE(Seed + “$”)+ Digest)]}, Encrypt算法采用3-DES加密,用md5加密的key作为密钥匙。

例 1.4. 3-DES加密的实现代码

public String getSPTicketRequestValue()
{
String SPTicketRequestValue="";
try{
byte[] src = (getSeed() + "$" + getDigest() ).getBytes("UTF-16LE");
byte[] enKey = getEnKey(spKey);
byte[] encryptedData = Encrypt(src,enKey); (1)
String base64Encrypt = filter(base64en.encode(encryptedData)); (2)
String requestValue=spCode + "$" + base64Encrypt;
SPTicketRequestValue=URLEncoder.encode(requestValue);
}
catch(Exception e){
e.printStackTrace();
}
return SPTicketRequestValue;
}

public byte[] Encrypt(byte[] src,byte[] enKey)
{
byte[] encryptedData = null;
try
{
DESedeKeySpec dks = new DESedeKeySpec(enKey); (3)
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede"); (4)
SecretKey key = keyFactory.generateSecret(dks); (5)
Cipher cipher = Cipher.getInstance("DESede"); (6)
cipher.init(Cipher.ENCRYPT_MODE, key); (7)
encryptedData = cipher.doFinal(src); (8)
}
catch(Exception e)
{
e.printStackTrace();
}
return encryptedData;
}

private String filter(String str)
{
String output = null;
StringBuffer sb = new StringBuffer();
for(int i = 0; i < str.length(); i++)
{
int asc = str.charAt(i);
if(asc != 10 && asc != 13)
sb.append(str.subSequence(i, i + 1));
}
output = new String(sb);
return output;
}

(1) 调用3-DES的加密函数。

(2) base64编码3-DES的数据时,得到的字符串有换行符号,一定要去掉,否则uni-wise平台解析票根不会成功, 提示“sp验证失败”。在开发的过程中,因为这个问题让我束手无策,一个朋友告诉我可以问联通要一段加密后 的文字,然后去和自己生成的字符串比较,这是个不错的调试方法。我最后比较发现我生成的字符串唯一不同的 是多了换行。我用c#语言也写了票根请求程序,没有发现这个问题。

(3) 从原始密匙数据创建DESedeKeySpec对象。

(4) 创建一个密匙工厂,然后用它把DESKeySpec转换成一个SecretKey对象。

(5) 根据密匙工厂,得到一个密钥匙实例。

(6) 创建Cipher对象。

(7) 初始化Cipher对象(带入密钥匙)。

(8) 执行加密操作。


为方便调试给出一段加密后的字符串(用自己的代码加密同样的字符串后得到的结果和给出的结果进行比较)

content = key = 1234;
result = base64(3des(contentbyte,keybyte));
result : "25Pxmw/+/qKg2arQpLdvqQ=="

1.3. 解析响应票根
uni-wise平台收到请求票根后会进行解析票根,然后用户登录后uni-wise又会将登录信息放在响应票根中,传回给请求票根者。 (传回的地址是请求票根中的”returnUrl“)。传输的协议也是http+xml.

1、UrlEncoding的解码(笔者在测试中发现在uni-wise测试平台首先需要对所得的票根响应串进行urlEncoding解码,这也是接口 遵照接口手册进行的,但是在uni-wise正式平台下得到的响应票根已经是对urlEncoding解码的)。

import java.net.URLDecoder;
URLDecoder.decode(strEncoding);

2、分割响应票根,得到加密的字符串。分割很简单,以"$"为分割符,得到"$"后面的字符串即可。

3、对加密字符串进行base64解码。

import sun.misc.BASE64Decoder;
BASE64Decoder base64Decode = new BASE64Decoder();
base64Decode.decodeBuffer(strEnBase64);

4、对base64解码的值进行3-DES解密(密钥匙等同于加密的密钥匙)。

public String deCrypt(byte[] debase64)
{
String strDe = null;
Cipher cipher = null;
try
{
cipher=Cipher.getInstance("DESede");
byte[] key = getEnKey(spKey); (1)
DESedeKeySpec dks = new DESedeKeySpec(key);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey sKey = keyFactory.generateSecret(dks);
cipher.init(Cipher.DECRYPT_MODE, sKey); (2)
byte ciphertext[] = cipher.doFinal(debase64);
strDe = new String(ciphertext,"UTF-16LE");
}
catch(Exception ex)
{
strDe = "";
ex.printStackTrace();
}
return strDe;
}

(1) 得到密钥匙,具体实现请参考请求票根部分

(2) 加密、解密其实就是指定这个类型 Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE


5、通过"$"分割解密的字符串,然后取得对应的信息,如MDN,UserId等。如果SP需要记录用户信息,应该在解析票根后 持久存储。然后根据实际需要将用户信息加入cookies.以实现SSO。

第 2 章 预定接口
2.1. 发起预定请求
用户可以从SP平台和uni-wise平台发起预定请求,当从SP发起预定请求时,SP平台需要 构建预定请求的数据,以htpp协议传输给uni-wise.请求数据的构建有两种方式一种是 service方式,一种是product方式.根据实际情况构建请求参数。 http://接入平台的URL?SPCode=A&ServiceCode=B&ReturnURL=C (service方式) http://接入平台的URL?SPCode=A&ProductCode=B&ReturnURL=C (product方式)

2.2. 解析uni-wise预定请求
所 有的预定操作都是链接到uni-wise预定平台,然后用户在此发生预定关系,uni-wise构建 预定信息通知SP平台,根据SP的响应结果,决定预定动作成功与否。uni-wise要将预定信息 通知SP,因此需要预先知道SP接收预定的链接,这是在申请彩e业务时完成。

例 2.1. 解析预定请求

InputStream subscriptionRequestStream = request.getInputStream(); (1)
PraseXml(subscriptionRequestStream); (2)

(1) uni-wise将预定信息以输出流传输到SP平台,SP用HttpServletRequest得到输入流。

(2) ParseXml为解析xml的函数。与解析普通的xml一样,因此在此不再描述其实现方法。


2.3. 验证请求
得到预定请求信息后,应该对其数据进行验证,根据验证的结果返回相应的信息给uni-wise平台。

if (mdn == null || mdn.trim().equals(""))
{
errorCode = "16842754";
errorInfo = "cannot find MDN";
}

if ( spCode == null || spCode.trim().equals(""))
{
errorCode = "16973826";
errorInfo = "cannot find SPCode";
}

if (productCode == null || productCode.trim().equals(""))
{
errorCode = "17104898";
errorInfo = "cannot find ProductCode";
}
if (transactionID == null || transactionID.trim().equals(""))
{
errorCode = "17170434";
errorInfo = "cannot find TransactionID";
}

2.4. 响应预定请求
SP接收到uni-wise预定请求信息后进行验证核对,然后响应请求,传输协议为http+xml。 uni-wise得到响应后执行预定动作,返回给用户预定相关信息。SP处理请求结果有成功 和失败两种情况。

例 2.2. 请求成功的响应格式

StringBuffer sb = new StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
sb.append("<U-MAX>\n");
sb.append("<PreSubscriptionNotify>\n");
sb.append("<MDN>" + getMdn() + "</MDN>\n");
sb.append("<SPCode>" + getSpCode() + "</SPCode>\n");
sb.append("<ProductCode>" + getProductCode() + "</ProductCode>\n");
sb.append("<TransactionID>" + getTransactionID() + "</TransactionID>\n");
sb.append("</PreSubscriptionNotify>\n");
sb.append("</U-MAX>");
String strResponse = new String(sb);

例 2.3. 请求失败的响应格式

StringBuffer sb = new StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
sb.append("<U-MAX>\n");
sb.append("<ValidError>\n");
sb.append("<ValidErrorCode>");
sb.append(getErrorCode());
sb.append("</ValidErrorCode>");
sb.append("<ValidErrorInfo>");
sb.append(getErrorInfo());
sb.append("</ValidErrorInfo>");
sb.append("</ValidError>\n");
sb.append("</U-MAX>");
String strResponse = new String(sb);


第 3 章 预定取消接口
3.1. 发起取消请求
用户可以从SP平台和uni-wise平台发起取消请求,当从SP发起取消请求时,SP平台需要 构建取消请求的数据,以htpp协议传输给uni-wise.请求数据的构建有两种方式一种是 service方式,一种是product方式.规则同预定请求一致。

3.2. 解析uni-wise取消请求
所 有的取消操作也是定向到uni-wise平台,然后用户在此执行取消动作,uni-wise接收 到用户取消动作后将取消信息通知SP平台,根据SP的响应结果,决定取消动作成功与否。 SP的取消接收链接,也是在申请彩e业务时完成。实现代码与解析预定请求代码一致,在此 不再重复。

3.3. 验证请求
请参考预定请求章节的相应内容。

3.4. 响应请求
例 3.1. 请求处理成功的响应

StringBuffer sb = new StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
sb.append("<U-MAX>\n");
sb.append("<SubscriptionCancel>\n");
sb.append("<MDN>" + getMdn() + "</MDN>\n");
sb.append("<SPCode>" + getSpCode() + "</SPCode>\n");
sb.append("<ProductCode>" + getProductCode() + "</ProductCode>\n");
sb.append("<TransactionID>" + getTransactionID() + "</TransactionID>\n");
sb.append("</SubscriptionCancel>\n");
sb.append("</U-MAX>");
String strResponse = new String(sb);

例 3.2. 请求处理失败的响应

StringBuffer sb = new StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
sb.append("<U-MAX>\n");
sb.append("<ValidError>\n");
sb.append("<ValidErrorCode>");
sb.append(getErrorCode());
sb.append("</ValidErrorCode>");
sb.append("<ValidErrorInfo>");
sb.append(getErrorInfo());
sb.append("</ValidErrorInfo>");
sb.append("</ValidError>\n");
sb.append("</U-MAX>");
String strResponse = new String(sb);

第 4 章 web push接口
发送彩e,先提交到uni-wise平台的push接口,然后再由uni-wise push到IMAP平台,如图:


图 4.1.

4.1. 提交push
SP 提交信息到 uni-wise Push接口的数据分包头和包体,其中包体以MIME格式传输。

1、 构建包头: SP的企业代码(不加密)+ SP的密钥(加密)+付费代码(加密)+条件类别(加密)+条件代码(加密) +发送方式(加密)+[发送开始时间(加密)+截止时间(加密)]+ 计费手机号码(加密)+ MIME包体 中的边界字段Boundary(加密)

import java.net.HttpURLConnection;
import java.net.URL;

private Url url = null;
HttpURLConnection conn = null;

//先建立URL长连接
public void connectUrl(String strUrl)
{
try
{
url = new URL(strUrl);
conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("post");
}
catch(Exception ex)
{

}
}

//设置包头
public void setHeader()
{
conn.setRequestProperty("SPCode", spCode);
conn.setHeader("EncryptSPKey",enKey);
conn.setHeader("FeeCode",enFeeCode );
conn.setHeader("ConditionType",enConditionType);
conn.setHeader("ConditionCode", enConditionCode);
conn.setHeader("SendType", enSendType);
conn.setHeader("StartTime", enStartTime);
conn.setHeader("EndTime",enEndTime);
conn.setHeader("ThirdPartyPayPhone",enThirdPartyPayPhone);
conn.setHeader("Boundary", enBoundary);
}

包头中除了spcode不用加密,其余的都遵行base64(3des(contentbyteplus,keybyte))加密方式。 加密的实现代码和前面章节描述的一致。

2、构建包体

MIME (Multipurpose Internet Mail Extensions,多目的Internet邮件扩展)是创建用于电子邮件交换, 网络文档,及企业网和Internet上的其他应用程序中的文件格式的规范.

例 4.1. 构建MIME格式的包体

private MimeMessage mime;
//构造MIME格式的包体
private void setMimeMessage()
{
try
{
mime.setFrom(new InternetAddress(strFrom)); (1)
mime.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(strTo)); (2)
mime.setSubject(strSubject,"UTF-8"); (3)
mime.setSentDate(new Date());
mime.setContent(getMimeMultipart());
}
catch(Exception ex)
{
}
}
private MimeMultipart getMimeMultipart()
{
MimeMultipart mimeMultipart = new MimeMultipart();
MimeBodyPart mimeBodyPart = new MimeBodyPart();
try
{
mimeBodyPart.setText((String)mimeBodyText.get(i),"UTF-8"); (4)
mimeMultipart.addBodyPart(mimeBodyPart);
Vector filePathes = getFilePaths();
for(int i = 0; i < filePathes.size(); i++)
{
String filePath = (String)filePathes.get(i);
javax.activation.DataSource datasource = new FileDataSource(filePath);
MimeBodyPart mimeFile = new MimeBodyPart();
mimeFile.setDataHandler(new DataHandler(datasource));
mimeFile.setFileName((new File(filePath)).getName());
mimeMultipart.addBodyPart(mimeFile);
}
}
catch(Exception e)
{

}
return mimeMultipart;
}

(1) 设置发送地址,在手机上显示发送方为该值

(2) 设置要发送到的手机号

(3) 手机上显示的标题值,经过笔者测试如果不指定编码为UTF-8,手机上显示为乱码(测试手机京瓷), 不知道其他手机是否有这种情况。

(4) 手机上显示的正文,经过笔者测试如果不指定编码为UTF-8,手机上显示为乱码(测试手机京瓷)。


3、提交内容到push接口

public void write(String body)
{
java.io.OutputStream outStream = conn.getOutputStream(); (1)
DataOutputStream dataOutStream = new DataOutputStream(outStream);
dataOutStream.writeBytes(body);
dataOutStream.flush();
dataOutStream.close();
}

(1) 这里的conn对象是引用第一步中实例的URLConnection对象,已经处于open状态。


4、读取push响应信息

提交信息与返回信息是实时的,因此应该在提交后即实现读取操作。

public String responsePush()
{
StringBuffer sb = null;
try
{
sb = new StringBuffer("");
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
for(String line = null; (line = rd.readLine()) != null;)
sb.append(line);
rd.close();
}
catch(Exception ex)
{

}
return new String(sb);
}

1-8 of 8