一种基于异步通讯方式的校园信息机终端验证方法

文档序号:7713573阅读:202来源:国知局
专利名称:一种基于异步通讯方式的校园信息机终端验证方法
技术领域
本发明涉及教育信息化行业的校讯通远程終端设备到服务器端验证技木,尤其是在高并发情况下,NETTY NIO异步通讯方式的校园信息机终端验证方法。
背景技术
随着教育信息化的推广,依托数字化校园终端,利用短信、语音、互联网等多种手段,为学生家长提供信息互动、考勤管理、亲情电话,位置服务的业务已经得到了广大教师、家长的接受和认可,数字化校园终端在学校的覆盖率越来越高。目前,各移动分公司都在对 现有数字化校园业务进行需求整合,提供统一平台,集中式开展数字化校园业务。我们假设每学校4部考勤终端机,每省I万所学校,那么省平台同一时间集中认证的远程终端将达到4万台。由于考勤終端机与服务器始终采用长连接的方式,服务器端处理压力非常大。传统的校讯通终端设备到服务器端认证的做法是采用标准的阻塞式Socket IO的通讯模式,服务器端打开侦听的端ロ,一有客户端连接就至少创建两个新的线程来负责这个连接。一个负责客户端发送的信息,另ー个负责通过该Socket发送数据。如果4万台终端同时到服务器端认证,则服务器端进程需要派生8万个处理线程。一个进程能处理的客户端请求是有限的。当面向高并发的客户端连接时,经常会出现服务器端连接超时的情況,考勤设备掉线情况时有发生,极大程度上影响了用户的感知。普遍的做法都是采用集群方案,通过线性増加服务器硬件数量的手段,来提升服务端的处理能力。或者在服务器上部署多套相同的服务端认证程序,分别启用不同的端ロ来响应客户端的请求。由于客户端到服务端认证,需要明确声明注册的服务器地址和端ロ。无论是采用服务器硬件扩容的方案还是采用应用软件部署策略调整的方案,由于远程終端无法自适应切换连接,只能人工到场逐台終端进行參数配置更新。考勤终端的管理和维护的难度加大了很多。撇开服务器硬件扩容成本不谈,以上2种方案显然都不具备规模上的扩展性。

发明内容
本发明目的是为了解决高并发情况下,校园信息机到服务器端注册的信息阻塞问题,提升到服务器处理性能和扩展能力。本发明的技术解决方案是采用JBOSS (JB0SS是SUN公司一个开源的符合J2EE规范的应用服务器)的NETTY3 NIO的异步通讯机制,来提升服务端的处理能力和扩展能力,boss线程负责nio socket的connect事件,连接成功后,由worker线程进行read, write处理,由于底层核心使用了 Iinux 2. 6的epoll,大大节约了 socket的轮循事件,同时在业务处理中启用了 memcache和消息队列,增强了业务处理的性能,从而只要使用少量线程就可以服务大量客户端,节约了服务器端的内存消耗。我们基于异步,网络和事件驱动的NETTY应用程序框架进行开发,对于数字化校园信息終端机到服务器端的认证能力的提升,能够不通过硬件能力扩展,即可适应高并发的业务特征需求。协议服务器和协议客户端具有快速敏捷、高性能、高灵活性的特点。Netty 架构主要是 ChannelFactory 和 ChanneIPipeIineFactory。ChannelFactory主要生产网络通信相关的Channel实例和ChannelSink实例,ChannelPipelineFactory关注于具体传输数据的处理,同时也包括异常处理等其他方面的内容,可以根据实际需要向内添加相应的handler, —般ChannelPipelineFactory需要自己实现,因为传输数据的处理及其他操作和业务关联比较紧密,需要自定义处理的handler。第一步实例化一个Bootstrap,并且通过构造方法指定一个ChannelFactory实现
第二步向bootstrap 实例注册'一个自己实现的 ChannelPipelineFactory第三步!bootstrap,bind (new InetSocketAddress (port)),然后等待客户端来连接,在连接完成后,会发起类型为CONNECTED的ChannelStateEvent,并且开始在自定义的Pipeline里面流转,如果注册■的handler有这个事件的响应方法的话那么就会调用到这个方法。此后就是数据的传输。Netty NIO主要通过ー个BOSS线程处理等待链接的接入,若干个WORKER线程(从worker线程池中挑选ー个赋给Channel实例,因为Channel实例持有真正的java网络对象)接过BOSS线程递交过来的CHANNEL进行数据读写并且触发相应事件传递给pipeline进行数据处理。如果是ー个网络会话最末端的事件,比如messageRecieve,那么可能在某个handler里面就直接结束整个会话,并把数据交给上层应用,但是如果是网络会话的中途事件,比如connect事件,那么当触发connect事件时,经过pipeline流转,最终会到达挂载pipeline最底下的ChannelSink实例中,这类实例主要作用就是发送请求和接收请求,以及数据的读写操作。我们应用了 Netty封装的丰富的缓冲技术ByteBuffer。ByteBuffer系统对外统ー的接ロ就是ChanneIBufTer,这个接ロ从整体上来说定义了两类方法,一种是类似getXXX(int index…),setXXX(int index…)需要指定开始操作buffer的起始位置,简单点来说就是直接操作底层buffer,并不用到Netty特有的高可重用性buffer特性,另外一种是类似readXXXO , writeXXXO不需要指定位置的buffer操作,这类方法实现放在了AbstractChannelBuffer,其主要的特性就是维持buffer的位置信息,包括readerlndex,writerlndex,以及回溯作用的 markedReaderlndex 和 markedWriterlndex,当用户调用readXXXO 或者 writeXXX()方法时,AbstractChannelBuffer 会根据维护的 readerlndex,writerlndex计算出读取位置,然后调用继承自己的ChannelBuffer的getXXX(intindex…)或者setXXX(int index…)方法返回结果,这类方法在Netty内部被大量调用,因为这个特性最大的好处就是很方便地重用buffer而不必去费心费カ维护index或者新建大量的ByteBuffer。我们通过channel系统对java网络做了ー层封装,加上了 SEDA特性(基于事件响应,异步,多线程等)。其最終的网络通信还是依靠底下的java网络api。从channel的疋义来说,write, bind, connect, disconnect, unbind, ClosejnIlrM^ife setlnterestOps等方法都会返回ー个channelFuture,这这些方法调用都会触发相关网络事件,并且在pipeline中流转。Channel很多方法调用基本上不会马上就执行到最底层,而是触发事件,在pipeline中走ー圈,最后才在channelsink中执行相关操作,如果涉及网络操作,那么最终调用会回到 Channel 中,也就是 serversocketchannel, socketchannel, serversocket,socket等java原生网络api的调用。Netty提供了全面而又丰富的网络事件类型,其将java中的网络事件分为了两种类型Upstream和Downstream。一般来说,Upstream类型的事件主要是由网络底层反馈给 Netty 的,比如 messageReceived, channel Connected 等事件,而 Downstream 类型的事件是由框架自己发起的,比如bind, write, connect, close等事件。实现方式是某个具体Handler 通过继承 ChannelUpstreamHandler 和 ChannelDownstreamHandler 类来进行区分。PipeLine在Downstream或者Upstream类型的网络事件发生时,会调用匹配事件类型的Handler响应这种调用。ChannelPipeline维持有所有handler有序链表,并且由handler自身控制是否继续流转到下一个handler (ctx. sendDownstream (e),这样设计有个好处就是随时终止流转,业务目的达到无需继续流转到下ー个handler)。
具体实施方式

以下是基于netty nio技术实现校讯通考勤信息机到服务器端注册的代码样例。PhoneSession维护了当前服务上连接的客户端状态信息.PhoneDecoder主要是由于解析亲情电话数据包协议PhoneEncoder是讲业务对象转换为亲情电话数据包协议KeepOnlineMonitor主要是通过遍历当前服务端连接的客户端发送心跳包维持连接·PhoneServerHandler主要是将decode解析后的java对象进行业务上的派分处理·PhoneSession
package com. linkage, justone4. phone;import org. jboss. netty. channel. Channel;import java. util. Map;import java. util.HashMap;
import java. util, concurrent. ConcurrentHashMap;
public class PhoneSession {
private static MapくPhoneServerHandler, Channel) sessions = newConcurrentHashMap ();
private static Map<String, PhoneStatus> phoneStatusMap ニ new HashMapO ;
public static MapくPhoneServerHandler,Channel〉getAllSessions() { return sessions;
}
public static MapくString, PhoneStatus) getPhoneStatusMap() {return phoneStatusMap;
}
public static PhoneStatus getPhoneStatus(String deviceld) {PhoneStatus result = phoneStatusMap. get(deviceld);if (result == null) {
result = new PhoneStatus();phoneStatusMap. put(deviceld,result);
return result;
}
public static void sessionOpened(PhoneServerHandler handler, Channelchannel) {
sessions.put(handler, channel);getPhoneStatus(handler. getDeviceldO). setLoginTime(System. currentTimeMillis,}
public static void sessionClosed(PhoneServerHandler handler) {sessions.remove (handler);
getPhoneStatus(handler. getDeviceld())· setExitTime(System. currentTimeMillis(
, }
I
PhoneServerHandler
package com. linkage. justone4. phone;
import org. jboss. netty. channel.氺;
import java. util, concurrent, atomic. AtomicLong;
import java. util, concurrent, atomic. AtomicInteger; import java. net. InetSocketAddress;
import com.linkage. justone4. phone, command. CommandFactory;import com. linkage. justone4. phone, command. Command;import com. linkage. justone4. phone, packet. BasicPacket;
SChannelPipelineCoverage (〃one〃)
public class PhoneServerHandler extends SimpleChannelHandler {public static int IDLE—TIME = 3* 1000 * 60; //one minutes
//登录指令与心跳包指令不需要验证
private final static String[] NON_AUTHENTICATED—COMMANDS = newString[]{BasicPacket. PHONE—AUTHEN_FUNC—ID,
BasicPacket. CONNECT_STATUS_FUNC_ID};
private boolean logined = false;private String deviceld = null;private String srclp = null;
private boolean positiveKeepOnline = false; //是否主动发送了心跳包
private long IastAccessTime = System. currentTimeMillis O ; //最近一次
private long IastSendStatusTimeこO; //最近一次公话状态查询时间 private static AtomicInteger clientCount = new AtomicIntegerO ;
public static int clientCount() {return clientCount. intValueO ;
private boolean isCommandOkWithoutAuthentication(String command) { boolean okay = false;
for (String allowed : NON_AUTHENTICATED_COMMANDS) { if (allowed, equals(command)) { okay = true; break;
}
}
return okay;
}
◎Override
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)throws Exception {
super.handleUpstream(ctx, e);
}
Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
{
IastAccessTime = System. currentTimeMillis();
BasicPacket packet = (BasicPacket)e. getMessage();if(!logined
&& !isCommandOkWithoutAuthentication(packet. packetFuncNo)) {e. getChannel (). close ();return;
}
Commandcommand=
CommandFactory. getCommand(packet. packetFuncNo);if(command != null) {
command, execute (this, e. getChannel (), packet);
}
}
Override
public void channelDisconnected(ChannelHandlerContext ctx,ChannelStateEvent e) throws Exception {
clientCount. decrementAndGet();
PhoneSession. sessionClosed(this);
}
Override
public void channelConnected(ChanneIIIandlerContext ctx,ChannelStateEvent e) throws Exception {clientCount. addAndGet(I);
InetSocketAddressaddr=
(InetSocketAddress)e. getChannel(). getRemoteAddress();
srclp = addr. getAddress(). getHostAddress();
} Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent
e) {
e. getChannel (). close ();
}
public boolean isIdleO {
return System. currentTimeMillis() - IastAccessTime > IDLE—TIME;
} —
public boolean isLoginedO {return logined;
}
public String getDeviceldO {return deviceld;
}
public void IoginSuccess(String deviceld) { this, logined = true; this.deviceld = deviceld;
}
public void setPositiveKeepOnline(boolean positiveKeepOnline) { this. positiveKeepOnline = positiveKeepOnline;
public boolean isPositiveKeepOnline() { return this. positiveKeepOnline;
}
public boolean needSendStatus() {
return System. currentTimeMillis() - this. IastSendStatusTime > 24
*60 * 60 * 1000;
}
public void setLastSendStatusTime(long time) { this.IastSendStatusTime = time;
public String getSrclpO {return srclp;
}
public static void main (String[] args) {
System, out. println(24 * 60 * 60 * 1000);
}
}对于请求协议的编码解码,按照协议格式操作ChannelBuffer中的字节数裾。 FrameDecoder内部维护了ー个DynamicChannelBuffer成员来存储接收到的数据,它就像
个抽象模板,把整个解码过程模板写好了,子类只需实现decode函数即可。PhoneEncoder:
package com. linkage.justone4. phone;
import org. jboss. netty.channel. SimpleChannelHandler;
import org. jboss. netty. channel. ChannelPipelineCoverage;
import org. jboss. netty.channel. ChannelHandlerContext;
import org. jboss. netty.channel. MessageEvent;
import org. jboss. netty. buffer. ChannelBuffer;
import org. jboss. netty. buffer. ChannelBuffers;
import org. apache.log4j. Logger;
import com. linkage. justone4. phone, packet. BasicPacket;
◎ChannelPipelineCoverage("all")
public class PhoneEncoder extends SimpleChannelHandler {
private static Logger logger =しogger. getLogger(PhoneEncoder.class);private boolean server = true;
Override
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e)throws Exception {
if(!(e. getMessage() instanceof BasicPacket)) {ctx. sendDownstream(e);return;
}
BasicPacket packet ニ (BasicPacket)e. getMessage ();if (server)
packet. encodeResponsePacket ();
else
packet. encodeRequestPacket ();
ChannelBufferbuffer=
ChannelBuffers. dynamicBuffer(packet. packetLength);
buffer. writeBytes(packet. packetBody);if(server)
logger, info(〃发送到设
备"+((PhoneServerHandler)ctx. getPipeline(). get("handler")). getDeviceld()+〃, + packet);
e. getChannel (). write (buffer);
}
public PhoneEncoder(boolean server) {this.server - server;
}
}
PhoneDecoder
package com. linkage. justone4. phone;
import org. jboss. netty. handler, codec, frame. FrameDecoder;

import org. jboss. netty. channel. ChannelHandlerContext;import org. jboss. netty. channel. Channel;import org. jboss. netty. buffer. ChannelBuffer;import org. apache. log4j.しogger;
import com. linkage. justone4. phone, packet. PacketFactory;import com. linkage. justone4. phone, packet. BasicPacket;
@org.jboss. netty.channel. ChannelPipelineCoverage(〃one〃)public class PhoneDecoder extends FrameDecoder {
private static Logger logger = Logger, getしogger(PhoneDecoder. class);private boolean server = true;private PacketFactory pf = null; pub丄ic PhoneDecoder(boolean server) {this.server = server;
pf = PacketFactory. getFactory(server O : I);
}
protected Object decode (ChannelHandlerContext ctx, Channel channel,ChannelBuffer buffer) throws Exception {
if (buffer. readableBytes () < 10) return null; //如果当前 buffer中的字节数〈头数据大小,不做处理
buffer. markReaderlndexO ;
String sLen ニ readString(buffer, 4); //获得此数据包大小 try {
int dataしength = Integer, parselnt〈Sしenj ;if (buffer. readableBytes() < dataLength - 4) {buffer. resetReaderlndex();
return null; //buffer中字节数〈数据包大小,不做处理
}
buffer. resetReaderlndex();
byte[] packetBuf ニ new byte[datalengthj;
buffer. readBytes !,packetBuf);
logger, debug (〃原始数据包"+new String (packetBuf)); BasicPacket packet = pf. getPacket(packetBuf); if (packet != null) { if (server)
packet. decodeRequestPacket();
else
packet. decodeResponsePacket ();
}
return packet;
} catch (Exception e) {e. printStackTrace ();
logger, warn(〃错误的数据包,连接将被关闭”); channel, close ();

return nulI;
}
}
protected String getString(ChannelBuffer buffer, int size) {byte[] result - new byte[size];buffer.getBytes(buffer, readerlndex(), result);return new String(result);
}
/林 *Helper method
*readString从buffer的当前位置开始读取size个字节,读取完后buffer 的index改变
*/
public static String readString(ChannelBuffer buffer, int size) ibyte[] result ニ new byte[size];buffer. readBytes(result);return new String(result);
}
}
KeepOnlineMoni tor
package com. linkage. justone4. phone;
import org. jboss.netty. channel. Channel;
import org. apache.log4j. Logger;
import java. util·*;
import java. text. SirapleDateFormat;
import com. linkage. justone4. phone, packet. ConnectStatusPacket;import com. linkage. justone4. phone, packet. PhoneStatusPacket;
/**
*主动维护心跳包线程*/
public class KeepOnlineMonitor extends Thread {
private Logger logger - Logger. getLogger(getClass ());public void run() {while(true) {
logger, debug (〃心跳检测&&公话状态同步执行〃); Map<PhoneServerHandler, Channel) alISessions = PhoneSession. getAllSessions();
//logger, debug(alISessions);
//logger, debug(PhoneSession. getPhoneStatusMap());Iterator<PhoneServerHandler>keys=
allSessions. keySet (). iterator ();whi Ie (keys. hasNext ()) {
PhoneServerHandler handler ニ keys.next ();
Channel channel = allSessions. get(handler);try {
if (handler. isLoginedO && channel. isConnectedO) {
if (handler. isIdleO) { channel, write(new ConnectStatusPacket()); handler. setPositiveKeepOnline(true);
}
if(handler. needSendStatus ()) {
PhoneStatusPacket packet ニ new
PhoneStatusPacket ();
packet. setCurTime(PhoneServerMain. phoneDB. getDatabaseTirae());
channel, write(packet);
handler. setLastSendStatusTime(System. currentTimeMillis());
}
PhoneStatusps=
PhoneSession. getPhoneStatus(handler. getDeviceld());
if(ps!ニnull)
ps.setCheckTime(System. currentTimeMillis());
}
}
catch(Exception e) {
logger, error (〃心跳检测&&公话状态同步线程异常〃,e);
}
}
try {
Thread, sleep(20*1000);
} catch (InterruptedException e) {e. printStackTrace ();
} 1
}
public static void main(String[] args) {
} }
权利要求
1.基于高并发的、NETTYNIO异步通讯方式的校园信息机终端验证方法,是ー套稳定高效的客户端-服务端Socket通讯服务,其特征是使用非阻塞式IO方式替代传统的SocketIO的工作模式,来解决高并发客户端认证请求情况下,服务器端处理效率的问题。其方法为 (1)使用新的buffer类型ChannelBuffer类来存储并操作读写的网络数据,依托NETTY框架自有的buffer系统,来减少复制过程中所帯来的内存消耗; (2)在事件处理上,通过ChannelPipeline来控制事件流,通过调用注册■其上的一系列ChannelHandler来处理事件。实现基于事件的过程流转以及完整的网络事件响应与扩展。
(3)服务端处理请求的过程主要是解码请求、业务逻辑处理、编码响应。
其步骤为 第一步实例化ー个Bootstrap,并且通过构造方法指定ー个ChannelFactory实现 第二步向bootstrap实例注册'一个自己实现的ChannelPipelineFactory 第三步!bootstrap, bind (new InetSocketAddress (port)),然后等待客户端来连接,在连接完成后,会发起类型为CONNECTED的ChannelStateEvent,并且开始在自定义的Pipeline里面流转,如果注册■的handler有这个事件的响应方法的话那么就会调用到这个方法。此后就是数据的传输。
2.根据权利要求I所述的高并发情况下的,基于NETTYNIO异步通讯方式的校园信息机终端验证技术,其特征是实现服务器端网络事件响应处理机制处理。包括bind- >accept- > read- > write过程,在网络交互的各个阶段发生时,触发相应事件交给初始化时生成的pipeline实例进行处理。事件处理都是通过Channels类的静态方法调用开始,将事件、channel传递给channel持有的Pipeline进行处理,Channels类几乎所有方法都为静态,提供ー种Proxy的效果(整个工程里无论何时何地都可以调用其静态方法触发固定的事件流转,但其本身并不关注具体的处理流程)。
全文摘要
本发明应用了JBOSS的NETTY3 NIO异步通讯技术,应用事件驱动模型替代了传统的发送和接收线程分离模式,解决了高并发情况下校讯通考勤终端终端到服务端认证信息阻塞问题,全面提升了服务器处理性能以及对资源的合理使用。
文档编号H04L29/06GK102694775SQ20111007051
公开日2012年9月26日 申请日期2011年3月23日 优先权日2011年3月23日
发明者刘本中, 刘洋, 司震, 孙立强, 宋炜伟, 王庆典, 王文波, 胡俊, 胡明慧, 郑国松, 陈翔宇, 魏阳 申请人:南京信通科技有限责任公司
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1