AndroidPn源码分析(一)
好了,开始研究源码了。目前对androidpn,只限于使用过它,跑了一下demo。现在开始研究一下源码。
(一)入口
当服务器端启动的时候,控制台会打印一些log,除了spring和hibernate,mina,在最后的几行,就是androidpn的代码了,第一个是XmppServer类。
在XmppServer中,加载spring的配置文件。这貌似把spring加载配置文件给略了,反正也没有web.xml中提到的application*.xml文件。
(二)web流程
启动的时候,也加载了配置中的一些action(springmvc里面,不知道是不是也是这么叫),在页面中,点击各个连接,会执行这些action信息。最主要的是send的那个方法。
public ModelAndView send(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String broadcast = ServletRequestUtils.getStringParameter(request,
"broadcast", "Y");
String username = ServletRequestUtils.getStringParameter(request,
"username");
String title = ServletRequestUtils.getStringParameter(request, "title");
String message = ServletRequestUtils.getStringParameter(request,
"message");
String uri = ServletRequestUtils.getStringParameter(request, "uri"); String apiKey = Config.getString("apiKey", "");
logger.debug("apiKey=" + apiKey); if (broadcast.equalsIgnoreCase("Y")) {
notificationManager.sendBroadcast(apiKey, title, message, uri);
} else {
notificationManager.sendNotifcationToUser(apiKey, username, title,
message, uri);
} ModelAndView mav = new ModelAndView();
mav.setViewName("redirect:notification.do");
return mav;
}
上面红色的字体,即是执行的方法,现在开始进入主题了。
(三)服务流程
广播的代码:
public void sendBroadcast(String apiKey, String title, String message,
String uri) {
log.debug("sendBroadcast()...");
log.debug("message content:"+message); //2014.8.7
IQ notificationIQ = createNotificationIQ(apiKey, title, message, uri);
for (ClientSession session : sessionManager.getSessions()) {
if (session.getPresence().isAvailable()) {
notificationIQ.setTo(session.getAddress());
session.deliver(notificationIQ);
}
}
}
在这里,首先是构造一个IQ类型的对象,基于xmpp协议的。
然后从服务器检查,是否有客户端存在,如果有客户端信息存在,就根据客户端的信息,将IQ发送到客户端。然后就看看客户端是怎么产生的。
(四)客户端连接
如果不知道该从哪里看起,那么可以用真机或者模拟器测试一下,看客户端连接的时候,服务器log里打印出了什么东西。
首先连接的时候,有这么一条首要记录:
<org.androidpn.server.xmpp.net.XmppIoHandler> : sessionCreated()...
好了,客户端连接的入口就是这个XmppIoHandler类了。
其实这个东西,是有配置的。在XmppServer启动的时候,加载spring-config.xml文件,在这个里面,有两个配置:
<bean id="xmppHandler" class="org.androidpn.server.xmpp.net.XmppIoHandler" /> <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor"
init-method="bind" destroy-method="unbind">
<property name="defaultLocalAddress" value=":5222" />
<property name="handler" ref="xmppHandler" />
<property name="filterChainBuilder" ref="filterChainBuilder" />
<property name="reuseAddress" value="true" />
</bean>
这个是mina的,这个东西之前没有接触过。但是客户端和服务器之间的通讯连接,是基于这个框架的。百度百科有很精简的一段介绍。
XmppIoHandler这个类,实现IOHandler接口中的几个方法。关于这几个方法,需要说明一下,不然真的是一头雾水,这里引用了一下网上的资料:
Handler用来处理MINA触发的I/O事件。IoHandler是一个核心接口,它定义了Filter链末端需要的所有行为。IoHandler接口包含以下方法: sessionCreated sessionOpened sessionClosed sessionIdle exceptionCaught messageReceived messageSent sessionCreated事件
一个新的connection被创建时,会触发SessionCreated事件。对于TCP来说,这个事件代表连接的建立;对于UDP来说,它代 表收到了一个UDP数据包。这个方法可以用作初始化session的各种属性,
也可以用来在一个新建的connection上触发一些一次性的行为。
I/O processor线程会调用这个方法,所以在实现该方法时,只加入一些耗时较少的操作,因为I/O processor线程是用来处理多会话的。
sessionOpened事件
当一个connection打开时会触发sessionOpened事件,这个事件永远在sessionCreated之后触发。如果配置了线程模式,那么这个方法会被非I/O processor线程调用。
sessionClosed事件
当一个session关闭的时候会触发sessionClosed事件。可以将session的清理操作放在这个方法里进行。
sessionIdle事件
当一个session空闲的时候会触发sessionIdle事件。当使用UDP时该方法将不会被调用。
exceptionCaught事件
当用户代码或MINA框架抛出异常时,会触发事件事件。如果该异常是一个IOException,那么connection会被关闭。
messageReceived事件
当接收到消息的时候会触发messageReceived事件。所有的业务处理代码应该写在这里,但要留心你所要的消息类型。
messageSent事件
当消息已被远端接收到的时候,会触发messageSent事件(调用IoSession.write()发送消息)。
从服务器的后台log中也可以发现,这些事件的创建顺序:sessionCreated() -- sessionOpened() -- messageReceived() -- messageSent() -- exceptionCaught() .
在创建session这个里面,并没有做什么事情。
关于sessionopen:
public void sessionOpened(IoSession session) throws Exception {
log.debug("sessionOpened()...");
log.debug("remoteAddress=" + session.getRemoteAddress());
// Create a new XML parser
XMLLightweightParser parser = new XMLLightweightParser("UTF-8");
session.setAttribute(XML_PARSER, parser);
// Create a new connection
Connection connection = new Connection(session);
session.setAttribute(CONNECTION, connection);
session.setAttribute(STANZA_HANDLER, new StanzaHandler(serverName,
connection));
}
在sessionOpen里面,则做了一些事情。首先IoSession这个类型,从eclipse的自动补全中,可以看到里面有很多属性,包括源码中列出的该session来自的地址ip。在这个里面,作者又给它增加了几个属性:XML_PARSER,CONNECTION,STANZA_HANDLER。三个分别均为对象。关于connection属性,应该是为了服务器发送消息的时候需要的。最后一个字段按字面意思来,是处理xml节点的。总的看来,这个方法是对从客户端发来的连接信息,进行了又一次的封装,增加了一些信息。
messageReceived(),当客户端连接的时候,服务器是会有消息接收的,收到的是客户端发来的,建立连接的一条消息:
<stream:stream to="192.168.10.100" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">
public void messageReceived(IoSession session, Object message)
throws Exception {
log.debug("messageReceived()...");
log.debug("RCVD: " + message); // Get the stanza handler
StanzaHandler handler = (StanzaHandler) session
.getAttribute(STANZA_HANDLER); // Get the XMPP packet parser
int hashCode = Thread.currentThread().hashCode();
XMPPPacketReader parser = parsers.get(hashCode);
if (parser == null) {
parser = new XMPPPacketReader();
parser.setXPPFactory(factory);
parsers.put(hashCode, parser);
} // The stanza handler processes the message
try {
handler.process((String) message, parser);
} catch (Exception e) {
log.error(
"Closing connection due to error while processing message: "
+ message, e);
Connection connection = (Connection) session
.getAttribute(CONNECTION);
connection.close();
}
}
这个方法里面,也做了一些事情。首先是拿到在sessionOpen中增加的STANZA_HANDLER对象,然后再拿到XMPP packet的解析器。下面就开始了StanzaHandler类的process方法,开始处理得到的xml信息的东西了。这个貌似是个大事件,还得擦亮枪慢慢来看,这个方法有点长,着实纠结分段看吧:
public void process(String stanza, XMPPPacketReader reader)
throws Exception {
boolean initialStream = stanza.startsWith("<stream:stream");
if (!sessionCreated || initialStream) {
if (!initialStream) {
return; // Ignore <?xml version="1.0"?>
}
if (!sessionCreated) {
sessionCreated = true;
MXParser parser = reader.getXPPParser();
parser.setInput(new StringReader(stanza));
createSession(parser);
} else if (startedTLS) {
startedTLS = false;
tlsNegotiated();
}
return;
}
如果发来的信息,不是以<stream:stream开头的,那直接就gg了,返回到 XmppIoHandler类的messageReceived方法,一切就over了。
里面很多的逻辑判断,如果创建了session或者所得到的节点信息是以<stream:stream开头的,这两个有一个符合条件的话,就会进入下面的判断。
客户端和服务器连接的时候,if (!sessionCreated)这个会确定执行,因为sessionCreated默认就是false。这里把客户端发来的:
<stream:stream to="192.168.10.100" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">
这段信息,放到了MXParser对象(这些是openfire官方的东西)中,然后调用了createSession方法,这个方法中有下列代码:
// Create the correct session based on the sent namespace
String namespace = xpp.getNamespace(null);
if ("jabber:client".equals(namespace)) {
session = ClientSession.createSession(serverName, connection, xpp);
if (session == null) {
StringBuilder sb = new StringBuilder(250);
sb.append("<?xml version='1.0' encoding='UTF-8'?>");
sb.append("<stream:stream from=\"").append(serverName);
sb.append("\" id=\"").append(randomString(5));
sb.append("\" xmlns=\"").append(xpp.getNamespace(null));
sb.append("\" xmlns:stream=\"").append(
xpp.getNamespace("stream"));
sb.append("\" version=\"1.0\">"); // bad-namespace-prefix in the response
StreamError error = new StreamError(
StreamError.Condition.bad_namespace_prefix);
sb.append(error.toXML());
connection.deliverRawText(sb.toString());
connection.close();
log
.warn("Closing session due to bad_namespace_prefix in stream header: "
+ namespace);
}
}
原本注释里面说这个是根据发送来的命名空间,来建立正确的session。即是这句:
session = ClientSession.createSession(serverName, connection, xpp);
这个是静态类,当进入这个方法时,我们就可以看到在控制台输出的一条log信息。
这里对connection做了一些处理,设置语言还有xmpp的版本信息。然后才是真正的创建ClientSession:
ClientSession session = SessionManager.getInstance()
.createClientSession(connection);
AndroidPn源码分析(一)的更多相关文章
- AndroidPn源码分析(二)
接上篇: (一)客户端与服务器建立连接 上一篇写到ClientSession createClientSession这里,创建一个客户端的session.在SessionManager类中创建了ses ...
- ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
- HashMap与TreeMap源码分析
1. 引言 在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...
- nginx源码分析之网络初始化
nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...
- zookeeper源码分析之五服务端(集群leader)处理请求流程
leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...
- zookeeper源码分析之四服务端(单机)处理请求流程
上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...
- zookeeper源码分析之三客户端发送请求流程
znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...
- java使用websocket,并且获取HttpSession,源码分析
转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
随机推荐
- DISK 100% BUSY,谁造成的?
iostat等命令看到的是系统级的统计,如果要追查是哪个进程导致的I/O繁忙,应该怎么办? iostat等命令看到的是系统级的统计,比如下例中我们看到/dev/sdb很忙,如果要追查是哪个进程导致的I ...
- C#类和接口、虚方法和抽象方法及值类型和引用类型的区别
1.C#类和接口的区别接口是负责功能的定义,项目中通过接口来规范类,操作类以及抽象类的概念!而类是负责功能的具体实现!在类中也有抽象类的定义,抽象类与接口的区别在于:抽象类是一个不完全的类,类里面有抽 ...
- Web页面报错: Eval()、XPath() 和 Bind() 这类数据绑定方法只能在上下文中使用
可以使用string.formt来避免出错. 如: <%# Convert.ToInt32(DataBinder.Eval(Container.DataItem, "Status&qu ...
- Python中实现异步并发查询数据库
这周又填了一个以前挖下的坑. 这个博客系统使用Psycopy库实现与PostgreSQL数据库的通信.前期,只是泛泛地了解了一下SQL语言,然后就胡乱拼凑出这么一个简易博客系统. 10月份找到工作以后 ...
- css高级应用及问题记录(持续更新)
css 参考手册: 1.http://css.doyoe.com/ 1.混合选择器样式定义: .button.icon:before { content: ""; po ...
- sql server 2000数据库 最近经常出现某进程一直占用资源,阻塞?死锁?
OA的数据库最近多次出现某进程一直占用资源,导致其他进程无法执行.使用sp_who2 和 sql server profiler跟踪查询,发现有以下几个语句常常占用资源: 1.declare @P1 ...
- 伟大的GCD和NSOperationQueue
一. GCD GCD中最重要的两个东西 任务 和 队列 任务就是一段代码(用来缓存,下载,计算等操作) 队列从大的方面分为两个队列:主队列(串行队列)和 自己创建的队列(串行,和并行) 主队列中: 在 ...
- JS、C#及SQL中的DateTime
一:SQL中的DataTime 1. between and 相当于>= and <= 2. 常用的将DataTime查询成字符串的方法 Select CONVER ...
- Python 5 —— OOP
OOP class MyClass: y = None def __init__(self,x,y): self.__x = x self.y = y def getx(self): return s ...
- React Native填坑之旅--与Native通信之iOS篇
终于开始新一篇的填坑之旅了.RN厉害的一个地方就是RN可以和Native组件通信.这个Native组件包括native的库和自定义视图,我们今天主要设计的内容是native库方面的只是.自定义视图的使 ...