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谢谢

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 诚e赊套现被骗了怎么办 钻戒小了怎么办小窍门 爱用商城逾期了怎么办 工程骗局洗脑了怎么办 还网贷客服电话打不通怎么办 中信客服电话打不通怎么办 亚马逊客服电话打不通怎么办 安卓软件闪退怎么办? 吃鸡麦克风炸麦怎么办 美版iphone屏摔了怎么办 天地荣域四十后怎么办 淘宝拍下商品不付款怎么办 淘宝买东西取消订单淘金币怎么办 淘宝淘金币快过期了怎么办 公众号密码忘了怎么办 酷狗直播个入驻粉丝少怎么办 健康证预约显示未接收怎么办 在互惠车贷逾期怎么办 天天中彩票怎么登陆不了怎么办 正宇新商城不能提现怎么办 融e购买完不发货怎么办 婴儿肚子胀气肚脐凸出来怎么办 微信商城买东西被骗了怎么办 拼多多商家不发货怎么办 鞋上的饰品掉了怎么办 饰品上的钻掉了怎么办 dota2饰品被好友礼物怎么办 dota2接收的礼物打不开怎么办 英雄联盟线上被压制怎么办 云联商城的钱怎么办 高顿财经不退款怎么办 拼多多连不上网怎么办 拼多多评分太低怎么办 拼多多限制提现怎么办 手机收不到验证码怎么办 淘宝未发货怎么取消退款怎么办 羊皮的鞋子刮坏怎么办 退货寄错了东西怎么办 毒app上买鞋尺码不合适怎么办 拼多多一直不发货怎么办 天猫国际买东西被税要退货怎么办