博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
即时通信系统Openfire分析之五:会话管理
阅读量:6644 次
发布时间:2019-06-25

本文共 9725 字,大约阅读时间需要 32 分钟。

  什么是会话?

  A拨了B的电话  电话接通  A问道:Are you OK?   B回复:I have a bug!  A挂了电话

  上面所喻整个过程就是所谓的会话。

 

  会话(Session)是一个客户与服务器之间的不中断的请求响应序列。注意其中“不中断”一词。

  Openfire的通信,是以服务器为中转站的消息转发机制,客户端与服务器要实现通信,必须保持连接,即持有会话。Session的管理,集中在SessionManager模块中。

  SessionManager

  SessionManager提供了一系列与Session生命周期相关的管理功能,例如:

// 创建public LocalClientSession createClientSession(Connection conn, StreamID id, Locale language) ;// 添加public void addSession(LocalClientSession session) ;// 获取public ClientSession getSession(JID from) ;// 移除public boolean removeSession(LocalClientSession session) ;......

  Session的整个生命周期,大致的讲可以分为:预创建、认证、移除

  •   预创建:在连接打开后,服务端收到客户端的第一个消息请求(即初始化流)时完成,此时的Session还不能用于通信
  •   认证:在资源绑定时完成,此时的Session被添加到会话管理队列以及路由表中,象征着已具备通信功能
  •   移除:当连接空闲或者关闭时,Session被移除

  特别注意的一点:

  预创建、认证这两个过程,是在客户端登录的时候完成,从后面分析中看到的回应报文,以及第一章《Openfire与XMPP协议》中提到登录报文协议,可以清楚的看到这一点。而移除则是在客户端掉线的时候完成。

 

  下面,就重点来看看,Openfire是具体是如何实现对Session的管理。

  Session 预创建

  回顾一下上一章的内容:ConnectionHandler类作为MINA的处理器,ConnectionHandler中的messageReceived()方法是消息的接收入口,接收到的消息交由StanzaHandler类处理。

  StanzaHandler.process()方法在处理消息时,首先调用本类中createSession()方法,完成了对Session的预创建。

abstract boolean createSession(String namespace, String serverName, XmlPullParser xpp, Connection connection)throws XmlPullParserException;

  上面的createSession()是一个抽象方法,由其子类完成。本文我们以C2S通信为研究对象,故其实现子类为:ClientStanzaHandler类

  ClientStanzaHandler.createSession()方法代码如下:

@Overrideboolean createSession(String namespace, String serverName, XmlPullParser xpp, Connection connection)        throws XmlPullParserException {    if ("jabber:client".equals(namespace)) {        // The connected client is a regular client so create a ClientSession        session = LocalClientSession.createSession(serverName, xpp, connection);        return true;    }    return false;}

这里创建了一个LocalClientSession类型的Session对象。

LocalClientSession.createSession()方法如下,只保留与创建流程相关的代码:

public static LocalClientSession createSession(String serverName, XmlPullParser xpp, Connection connection)        throws XmlPullParserException {      ......    // Create a ClientSession for this user.    LocalClientSession session = SessionManager.getInstance().createClientSession(connection, language);            // Build the start packet response    StringBuilder sb = new StringBuilder(200);    sb.append("
"); if (isFlashClient) { sb.append("
"); connection.deliverRawText(sb.toString()); // If this is a "Jabber" connection, the session is now initialized and we can // return to allow normal packet parsing. if (majorVersion == 0) { return session; } // Otherwise, this is at least XMPP 1.0 so we need to announce stream features. sb = new StringBuilder(490); sb.append("
"); if (connection.getTlsPolicy() != Connection.TLSPolicy.disabled) { sb.append("
"); if (connection.getTlsPolicy() == Connection.TLSPolicy.required) { sb.append("
"); } sb.append("
"); } // Include available SASL Mechanisms sb.append(SASLAuthentication.getSASLMechanisms(session)); // Include Stream features String specificFeatures = session.getAvailableStreamFeatures(); if (specificFeatures != null) { sb.append(specificFeatures); } sb.append("
"); connection.deliverRawText(sb.toString()); return session;}

  创建了一个LocalClientSession对象之后,服务端调用了两次deliverRawText()给客户端发送报文。从协议报文的内容来看,其实就是登录过程中,服务端收到客户端第一个初始化流之后的两个应答:第一个是流回复,第二个是通知客户端进行STL协商。

  而LocalClientSession是由SessionManager生成。

  SessionManager.createClientSession()代码如下:

 

public LocalClientSession createClientSession(Connection conn, Locale language) {    return createClientSession(conn, nextStreamID(), language);}

 

public LocalClientSession createClientSession(Connection conn, StreamID id, Locale language) {    if (serverName == null) {        throw new IllegalStateException("Server not initialized");    }    LocalClientSession session = new LocalClientSession(serverName, conn, id, language);    conn.init(session);    // Register to receive close notification on this session so we can    // remove  and also send an unavailable presence if it wasn't    // sent before    conn.registerCloseListener(clientSessionListener, session);    // Add to pre-authenticated sessions.    localSessionManager.getPreAuthenticatedSessions().put(session.getAddress().getResource(), session);    // Increment the counter of user sessions    connectionsCounter.incrementAndGet();    return session;} 

  由上面两个方法总结起来,Session的预创建流程为:

  (1)生成一个新streamID,并创建一个LocalClientSession对象的session

  (2)调用conn.registerCloseListener(),注册了Session的关闭监听。作用是当Connection关掉时,Session也相应清除掉

  (3)将生成的session添加到preAuthenticatedSessions队列中,表示预创建完成。但此时的Session并没有加入到路由表,还不能用来通信

  Session 认证

  在第一章,《Openfire与XMPP协议》一文中已经介绍,资源绑定其实是用户登录过程的其中一步。亦即,在这里完成了Session的认证。

  资源绑定是一个IQ消息,结合上一章《消息路由》中的分析,对于IQ消息,PacketRouterImpl模块使用IQRouter来完成路由。

  IQRouter.route()方法如下,其中只保留资源绑定部分代码:

public void route(IQ packet) {    ......    try {        ......        else if (session == null || session.getStatus() == Session.STATUS_AUTHENTICATED || (                childElement != null && isLocalServer(to) && (                    "jabber:iq:auth".equals(childElement.getNamespaceURI()) ||                    "jabber:iq:register".equals(childElement.getNamespaceURI()) ||                    "urn:ietf:params:xml:ns:xmpp-bind".equals(childElement.getNamespaceURI())))) {            handle(packet);        }         ......    }    catch (PacketRejectedException e) {        ......    }}

  其中,handle()方法创建了处理该IQ的IQHandler,并调用IQandler中的process()进行包处理。

  IQRouter.handle():

private void handle(IQ packet) {    JID recipientJID = packet.getTo();    ......    if (isLocalServer(recipientJID)) {        if (namespace == null) {            ......        }        else {            IQHandler handler = getHandler(namespace);            if (handler == null) {               ......            }            else {                handler.process(packet);            }        }    }    ......}

  传入的参数"namespace",是IQ的唯一标识码,将决定了这个IQ由谁来处理。

  资源绑定的namespace为:urn:ietf:params:xml:ns:xmpp-bind,也就是说,这个IQ,最终将交给IQBindHandler来处理。

  可以看到,IQBindHandler的构造方法:

public IQBindHandler() {    super("Resource Binding handler");    info = new IQHandlerInfo("bind", "urn:ietf:params:xml:ns:xmpp-bind");}

  包处理方法IQHandler.process():

@Overridepublic void process(Packet packet) throws PacketException {    IQ iq = (IQ) packet;    try {        IQ reply = handleIQ(iq);        if (reply != null) {            deliverer.deliver(reply);        }    }   ......}

  IQBindHandler.handleIQ()中,setAuthToken()方法实现对Session认证。

@Overridepublic IQ handleIQ(IQ packet) throws UnauthorizedException {    LocalClientSession session = (LocalClientSession) sessionManager.getSession(packet.getFrom());    IQ reply = IQ.createResultIQ(packet);    Element child = reply.setChildElement("bind", "urn:ietf:params:xml:ns:xmpp-bind");    ......    if (authToken.isAnonymous()) {        // User used ANONYMOUS SASL so initialize the session as an anonymous login        session.setAnonymousAuth();    }    else {        ......        session.setAuthToken(authToken, resource);    }    child.addElement("jid").setText(session.getAddress().toString());    // Send the response directly since a route does not exist at this point.    session.process(reply);    // After the client has been informed, inform all listeners as well.    SessionEventDispatcher.dispatchEvent(session, SessionEventDispatcher.EventType.resource_bound);    return null;}

  Session的认证后,其实就是将Session加入SessionManager中,如下:

LocalClientSession.setAuthToken():public void setAuthToken(AuthToken auth, String resource) {    ......    sessionManager.addSession(this);}

  在第四章分析消息路由时,发送消息前,首先用ToJID从路由表中获取Session,接着再进行消息路由。也就是说,一条消息能否被接收到,取决于接收者的Session是否存在于路由表中。

  而SessionManager.addSession()刚好就是将Session加入路由表,如下:

  SessionManager.addSession():

public void addSession(LocalClientSession session) {    routingTable.addClientRoute(session.getAddress(), session);    ....}

  此时,就代表了这个Session拥有了全部的功能,可以用来进行通信了。

  Session 移除

  移除工作就相对简单一些了,当监听到Connection关闭时,应清除掉相应的Session。

  在SessionManager的私有类ClientSessionListener实现了ConnectionCloseListener,能及时地监听到Connection关闭并进行Session的清除工作。监听是在Session预创建时注册,上文已经介绍。

  Session的关闭监听,ClientSessionListener类如下:

private class ClientSessionListener implements ConnectionCloseListener {    /**     * Handle a session that just closed.     *     * @param handback The session that just closed     */    @Override    public void onConnectionClose(Object handback) {        try {            LocalClientSession session = (LocalClientSession) handback;            try {                if ((session.getPresence().isAvailable() || !session.wasAvailable()) &&                        routingTable.hasClientRoute(session.getAddress())) {                    // Send an unavailable presence to the user's subscribers                    // Note: This gives us a chance to send an unavailable presence to the                    // entities that the user sent directed presences                    Presence presence = new Presence();                    presence.setType(Presence.Type.unavailable);                    presence.setFrom(session.getAddress());                    router.route(presence);                }                session.getStreamManager().onClose(router, serverAddress);            }            finally {                // Remove the session                removeSession(session);            }        }        catch (Exception e) {            // Can't do anything about this problem...            Log.error(LocaleUtils.getLocalizedString("admin.error.close"), e);        }    }}

  先关闭Sessoin,然后移除出队列,清除完毕!


Over!

 

转载于:https://www.cnblogs.com/Fordestiny/p/7487053.html

你可能感兴趣的文章
国资入场,P2P网贷平台星火钱包千万级A+轮融资
查看>>
windows server21012 r2 密钥
查看>>
北大发布新零售之城发展指数报告,上海超北京成榜首
查看>>
python urllib爬取网页编码问题
查看>>
JMS的常用方法
查看>>
隐私与机器学习,二者可以兼得吗?
查看>>
DNS原理概念详解
查看>>
shell数组的使用
查看>>
深入理解C语言的预编译指令之 include
查看>>
Performance Monitoring Per RDM and HBA
查看>>
新瓶如何和平改造老酒?(野生项目笔记01)
查看>>
Python实现MySQL DBA小工具一例
查看>>
YodaOS 中是如何生成 API 的?
查看>>
Android 中Fragment的自动重建
查看>>
在 iOS 平台实现Ping 和 traceroute
查看>>
小猿圈python学习-嵌套&匿名&高阶函数
查看>>
element-ui 版本升级对比
查看>>
js 整理
查看>>
OOM
查看>>
Java Socket基础(二)
查看>>