AndroidPN服务器源码简要分析
来源:互联网 发布:missrain 知乎 编辑:程序博客网 时间:2024/06/15 03:13
AndroidPN
https://github.com/dannytiehui/androidpn
郭霖 Android消息推送教学视频
http://www.imooc.com/learn/223
http://www.imooc.com/learn/358
Mina
官网 http://mina.apache.org/mina-project/index.html
资源
http://my.oschina.net/ielts0909/blog/92716?p=1#comments
http://my.oschina.net/makemyownlife/blog/122738
XMPP
官网 http://xmpp.org/
资源 http://www.cnblogs.com/hanyonglu/archive/2012/03/04/2378956.html
Mina
百度百科
ApacheMINA是一个网络应用程序框架,用来帮助用户简单地开发高性能和高可扩展性的网络应用程序。它提供了一个通过Java NIO在不同的传输例如TCP/IP和UDP/IP上抽象的事件驱动的异步API。
服务器
public class MinaTimeServer{ public static void main( String[] args ) throws IOException { IoAcceptor acceptor = new NioSocketAcceptor(); //日志过滤器 acceptor.getFilterChain().addLast( "logger", new LoggingFilter() ); //ProtocolCodecFilter该过滤器将二进制或者协议特定的数据转换为消息对象 //TextLineCodecFactory处理文本信息(编解码处理) acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter(new TextLineCodecFactory( Charset.forName( "UTF-8" )))); //设置逻辑处理器(服务于客户端的请求) acceptor.setHandler( new TimeServerHandler() ); acceptor.getSessionConfig().setReadBufferSize( 2048 ); //设置空闲时间 acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 ); }}
TimeServerHandler
public class TimeServerHandler extends IoHandlerAdapter{ @Override public void exceptionCaught( IoSession session, Throwable cause ) throws Exception { cause.printStackTrace(); } @Override public void messageReceived( IoSession session, Object message ) throws Exception {//服务器接收请求 String str = message.toString(); if( str.trim().equalsIgnoreCase("quit") ) { session.close(); return; } Date date = new Date(); session.write( date.toString() ); System.out.println("Message written..."); } @Override public void sessionIdle( IoSession session, IdleStatus status ) throws Exception {//服务器进入空闲状态 System.out.println( "IDLE " + session.getIdleCount( status )); } sessionOpen()...//服务器与客户端连接开启 sessionCreated()...//服务器与客户端连接创建 sessionClose()...//服务器与客户端连接关闭 messageSent()...//服务器发送消息,消息发送事件触发,当消息即响应发送(调用IoSession.write())}
自定义编解码
http://www.himigame.com/apache-mina/839.html
http://blog.csdn.net/a600423444/article/details/6671035
(以下代码出处 郭霖消息推送视频)
XmppDecoder
public class XmppDecoder extends CumulativeProtocolDecoder { // private final Log log = LogFactory.getLog(XmppDecoder.class); @Override public boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { int startPosition = in.position(); while(in.hasRemaing()){ byte b = in.get(); if(b=='\n'){ int currentPosition = in.position(); int limit = in.limit(); in.position(startPosition); in.limit(currentPosition); IoBuffer buf= in.slince(); byte[] dest = new byte[buf.limit()]; buf.get(dest); String str = new String(dest); out.write(str); in.position(currentPosition); in.limit(limit); } } }}
XmppEncoder
public class XmppEncoder implements ProtocolEncoder { public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { // log.debug("encode()..."); String s =null; if(message instanceof String){ s = (String)message; } if(s!=null){ CharsetEncoder charsetEncoder =(CharsetEncoder) session.getAttribute("encoder"); if(charsetEncoder==null){ charsetEncoder = Charset.defaultCharset().newEncoder(); session.setAttribute("encoder",charsetEncoder); } IoBuffer ioBuffer = IoBuffer.allocate(s.length); ioBuffer.setAutoExpanded(true); ioBuffer.putString(s,charsetEncoder); ioBuffer.flip(); out.write(ioBuffer); } public void dispose(IoSession session) throws Exception { // log.debug("dispose()..."); }}
XmppFactory
public class XmppCodecFactory implements ProtocolCodecFactory { private final XmppEncoder encoder; private final XmppDecoder decoder; /** * Constructor. */ public XmppCodecFactory() { encoder = new XmppEncoder(); decoder = new XmppDecoder(); } /** * Returns a new (or reusable) instance of ProtocolEncoder. */ public ProtocolEncoder getEncoder(IoSession session) throws Exception { return encoder; } /** * Returns a new (or reusable) instance of ProtocolDecoder. */ public ProtocolDecoder getDecoder(IoSession session) throws Exception { return decoder; }}
客户端
待续
Spring MVC + Mina
AndroidPN服务器端采用Spring MVC架构,那么是如何配置Mina的呢?
http://my.oschina.net/u/2319071/blog/465369
spring-config
<!--Mina逻辑处理器Handler--> <bean id="xmppHandler" class="org.androidpn.server.xmpp.net.XmppIoHandler" /> <!--Mina过滤器配置--> <bean id="filterChainBuilder" class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder"> <property name="filters"> <map> <entry key="executor"> <bean class="org.apache.mina.filter.executor.ExecutorFilter" /> </entry> <entry key="codec"> <bean class="org.apache.mina.filter.codec.ProtocolCodecFilter"> <constructor-arg> <!--配置自定义编码工厂--> <bean class="org.androidpn.server.xmpp.codec.XmppCodecFactory" /> </constructor-arg> </bean> </entry> <!-- <entry key="logging"> <bean class="org.apache.mina.filter.logging.LoggingFilter" /> </entry> --> </map> </property> </bean> <!--注入Mina NioSocketAcceptor--> <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 配置IdleTime空闲时间--> <bean id="getSessionConfig" factory-bean="ioAcceptor" factory-method="getSessionConfig"> <property name="readerIdleTime" value="30"></property> </bean>
AndroidPN项目结构
config
conf.security、config.properties、config存keystore、truststore等信息
jdbc
使用hibernate框架配置数据库ORM
spring-config
spring mvc架构 配置bean
dispatcher-servlet
spring mvc架构 负责责任分配
decorators
页面装饰
用户在线状态项目流程
V
页面采用decorators
WEB-INF decorators.xml
<decorators defaultdir="/decorators"> <excludes> <pattern>/includes/*</pattern> <pattern>/40*.jsp</pattern> <pattern>/dwr/*</pattern> </excludes> <decorator name="default" page="default.jsp"> <pattern>/*</pattern> </decorator></decorators>
excludes不需要装饰的页面
decorator name装饰器名字 page装饰器文件
pattern 需要使用装饰器的访问地址,可以配置多个
此处为/* 即WEB-INF pages下的所有
default.jsp
位置WebRoot下的decorates内
<div id="page"> <div id="header"> <jsp:include page="/includes/header.jsp"/> </div> <div id="content"> <ul id="tabmenu"> <li><a href="/index.do" class="<c:if test="${topMenu eq 'home'}"><c:out value="current" /> </c:if>">Home</a></li> <li><a href="/user.do" class="<c:if test="${topMenu eq 'user'}"><c:out value="current" /> </c:if>">Users</a></li> <li><a href="/session.do" class="<c:if test="${topMenu eq 'session'}"><c:out value="current" /> </c:if>">Sessions</a></li> <li><a href="/notification.do" class="<c:if test="${topMenu eq 'notification'}"><c:out value="current" /> </c:if>">Notifications</a></li> </ul> <div id="tabcontent"> <decorator:body/> </div> </div> <div id="footer"> <jsp:include page="/includes/footer.jsp"/> </div> </div>
四个选项卡index、 user、 session 、notification,点击各个选项卡会发生什么呢?
C
dispatcher-servlet.xml
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <value> /user_api.do=userapiController /notification_api.do=notificationapiController /index.do=filenameController /user.do=userController /session.do=sessionController /notification.do=notificationController </value> </property> <property name="order" value="1" /> </bean> <bean id="userapiController" class="org.androidpn.server.console.api.UserApiController"> <property name="methodNameResolver" ref="paramResolver" /> </bean> <bean id="notificationapiController" class="org.androidpn.server.console.api.NotificationApiController"> <property name="methodNameResolver" ref="paramResolver" /> </bean> <bean id="filenameController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController" /> <bean id="userController" class="org.androidpn.server.console.controller.UserController"> <property name="methodNameResolver" ref="paramResolver" /> </bean> <bean id="sessionController" class="org.androidpn.server.console.controller.SessionController"> <property name="methodNameResolver" ref="paramResolver" /> </bean> <bean id="notificationController" class="org.androidpn.server.console.controller.NotificationController"> <property name="methodNameResolver" ref="paramResolver" /> </bean>
index.do UrlFilenameViewController检查URL,查找到文件请求的文件名,并为之视图的名字,直接跳转到index.jsp页面
user.do 交由UserController进行逻辑控制处理
UserController
public class UserController extends MultiActionController { private UserService userService; public UserController() { //User数据操作服务 userService = ServiceLocator.getUserService(); } public ModelAndView list(HttpServletRequest request, HttpServletResponse response) throws Exception { //Presence顾名思义即用户在线状态 PresenceManager presenceManager = new PresenceManager(); //获取用户列表 List<User> userList = userService.getUsers(); for (User user : userList) { if (presenceManager.isAvailable(user)) {//判断用户状态,设置用户在线状态 // Presence presence = presenceManager.getPresence(user); user.setOnline(true); } else { user.setOnline(false); } // logger.debug("user.online=" + user.isOnline()); } ModelAndView mav = new ModelAndView(); //当前视图数据设置 mav.addObject("userList", userList); //当前视图位置WEB_INF的pages下的user内的list.jsp mav.setViewName("user/list"); //返回视图 return mav; }}
M
UserService
获取userservice bean实例
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");userService = context.getBean("userService");
spring-config.xml
<bean id="userDao" class="org.androidpn.server.dao.hibernate.UserDaoHibernate"> <property name="sessionFactory" ref="sessionFactory" /></bean><bean id="userService" class="org.androidpn.server.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao" /></bean>
UserServiceImpl继承UserService,对UserDao进行数据操作再封装;UserDaoHibernate继承UserDao,对User进行数据操作
UserDaoHibernate
public class UserDaoHibernate extends HibernateDaoSupport implements UserDao { public User getUser(Long id) {//根据id获取user return (User) getHibernateTemplate().get(User.class, id); } public User saveUser(User user) {//保存user getHibernateTemplate().saveOrUpdate(user); getHibernateTemplate().flush(); return user; } public void removeUser(Long id) {//根据id删除user getHibernateTemplate().delete(getUser(id)); } public boolean exists(Long id) { User user = (User) getHibernateTemplate().get(User.class, id); return user != null; } @SuppressWarnings("unchecked") public List<User> getUsers() {//获取所有user return getHibernateTemplate().find( "from User u order by u.createdDate desc"); } @SuppressWarnings("unchecked") public List<User> getUsersFromCreatedDate(Date createDate) { return getHibernateTemplate() .find("from User u where u.createdDate >= ? order by u.createdDate desc", createDate); } @SuppressWarnings("unchecked") public User getUserByUsername(String username) throws UserNotFoundException {//根据用户名获取user List users = getHibernateTemplate().find("from User where username=?", username); if (users == null || users.isEmpty()) { throw new UserNotFoundException("User '" + username + "' not found"); } else { return (User) users.get(0); } }}
User对象需要再hibernate.cfg.xml中配置
<mapping class="org.androidpn.server.model.User" />
服务器与客户端数据交互
服务器接收客户端数据(使用Mina框架)
XmppIoHandler Mina逻辑处理器Handler
... 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));//创建StanzaHandler } public void messageReceived(IoSession session, Object message) throws Exception {//服务器接收客户端请求数据 log.debug("messageReceived()..."); log.debug("RCVD: " + message); // Get the stanza handler 获取StanzaHandler 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(); } }...
StanzaHandler
process
public void process(String stanza, XMPPPacketReader reader) throws Exception { //客户端发过来的数据是否开头为"<stream:stream",若是则创建新的session会话 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)); //创建新的session createSession(parser); } else if (startedTLS) { startedTLS = false; //TLS处理 tlsNegotiated(); } return; } // If end of stream was requested if (stanza.equals("</stream:stream>")) {//会话结束 session.close(); return; } // Ignore <?xml version="1.0"?> if (stanza.startsWith("<?xml")) { return; } // Create DOM object Element doc = reader.read(new StringReader(stanza)).getRootElement(); if (doc == null) { return; } String tag = doc.getName(); if ("starttls".equals(tag)) { if (negotiateTLS()) { // Negotiate TLS startedTLS = true; } else { connection.close(); session = null; } } else if ("message".equals(tag)) {//处理message processMessage(doc); } else if ("presence".equals(tag)) {//处理presence log.debug("presence..."); processPresence(doc); } else if ("iq".equals(tag)) {//处理IQ log.debug("iq..."); processIQ(doc); } else { log.warn("Unexpected packet tag (not message, iq, presence)" + doc.asXML()); session.close(); } }
createSession 创建session会话
private void createSession(XmlPullParser xpp) throws XmlPullParserException, IOException { for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { eventType = xpp.next(); } // Create the correct session based on the sent namespace String namespace = xpp.getNamespace(null); if ("jabber:client".equals(namespace)) { //session实例化 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()); //写出流<?xml version='1.0' encoding='UTF-8'?><stream:steam... connection.deliverRawText(sb.toString()); connection.close(); log .warn("Closing session due to bad_namespace_prefix in stream header: " + namespace); } } }
Connection的deliverRawText 服务器流写出
private void deliverRawText(String text, boolean asynchronous) { log.debug("SENT: " + text); if (!isClosed()) { IoBuffer buffer = IoBuffer.allocate(text.length());//分配缓存大小 buffer.setAutoExpand(true);//可扩展缓存 boolean errorDelivering = false; try { buffer.put(text.getBytes("UTF-8")); buffer.flip(); if (asynchronous) { ioSession.write(buffer);//Mina写出iobuffer流 } else { // Send stanza and wait for ACK boolean ok = ioSession.write(buffer).awaitUninterruptibly( Config.getInt("connection.ack.timeout", 2000)); if (!ok) { log.warn("No ACK was received when sending stanza to: " + this.toString()); } } } catch (Exception e) { log.debug("Connection: Error delivering raw text" + "\n" + this.toString(), e); errorDelivering = true; } // Close the connection if delivering text fails if (errorDelivering && asynchronous) { close(); } } }
processIQ
processIQ处理IQ类型的请求数据(IQ类型的数据集成Packet)。
IQRouter路由将IQ数据根据namespace交由给指定IQHandler
IQRouter
private void handle(IQ packet) { ... IQHandler handler = getHandler(namespace);//根据namespace获取指定的路由器 if (handler == null) { sendErrorPacket(packet, PacketError.Condition.service_unavailable); } else { handler.process(packet); } ... }
用户信息注册User,添加新User,命名空间是jabber:iq:register,选择的IQHandler是IQRegisterHandler
IQRegisterHandler handleIQ
... userService.saveUser(user);//保存注册成功的用户...
此文结束,其余细节可自行分析了,XMPP协议需要继续分析学习。
望共勉之。O(∩_∩)O谢谢
- AndroidPN服务器源码简要分析
- AndroidPN客户端源码简要分析
- AndroidPN源码分析
- android下服务器推送实现 androidpn分析
- AndroidPN服务器与客户端代码分析
- AndroidPN服务器与客户端代码分析
- androidpn+tomcat推送消息源码流程分析
- Redis源码简要分析
- uboot源码简要分析
- Redis源码简要分析
- Redis源码简要分析
- Redis源码简要分析
- wifidog 源码简要分析
- uboot源码简要分析
- Retrofit源码简要分析
- Redis源码简要分析
- okHttp3源码简要分析
- Redis源码简要分析
- [LeetCode] Remove Invalid Parentheses
- MongoDB之安装
- IOS学习——TableView详细解释
- [leetcode287] Find the Duplicate Number
- Extjs 使用Cookie
- AndroidPN服务器源码简要分析
- 第10周 项目2 - 二叉树遍历的递归算法
- cocos2d-x 左下角的FPS,3行数字分别表示什么?
- 第十一周项目1-二叉树算法验证(哈夫曼编码的算法验证)
- swift 彻底解决_OBJC_CLASS_$_某文件名", referenced from:问题
- xcode常见错误处理备忘
- MAC下搭建SVN服务器
- 开源项目DataBaseManager(三):想要做到些什么
- 使用MyEclipse编写Html5代码,在浏览器中打开乱码问题