当前位置: 首页 > 工具软件 > ejabberd > 使用案例 >

Ejabberd源码解读-ejabberd_c2s模块

仲孙思源
2023-12-01

ejabberd_c2s模块由ejabberd_listener模块根据配置启动,启动过程可查看ejabberd_listener模块解读
根据ejabberd_c2s启动函数start/2

start(SockData, Opts) ->
    ?GEN_FSM:start(ejabberd_c2s,
           [SockData, Opts],
           fsm_limit_opts(Opts) ++ ?FSMOPTS).

可知ejabberd_c2s启动的是一个有限状态机gen_fsm
客户端与服务器建立连接过程如下(用户登录连接为例):
1 c status
wait_for_stream

2 c-send header

<?xml version='1.0'?>
    <stream:stream version='1.0' 
      xml:lang='en'        
      xmlns:stream='http://etherx.jabber.org/streams' 
      from='localhost' xmlns='jabber:client'>

3 s-send header

<?xml version='1.0'?>
    <stream:stream id='18098610605791176792'
      xml:lang='en' 
      xmlns:stream='http://etherx.jabber.org/streams' 
      from='localhost' 
      xmlns='jabber:client'>

4 s-send feature

 [{sasl_mechanisms,[<<"PLAIN">>,<<"DIGEST-MD5">>,<<"X-OAUTH2">>,<<"SCRAM-SHA-1">>]}]
<stream:features>
    <c ver='LSc28EBBWo2uA2P3nRDU+sBlbsc=' 
      node='http://www.process-one.net/en/ejabberd/' 
      xmlns='http://jabber.org/protocol/caps'/>
    <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
      <mechanism>PLAIN</mechanism>
      <mechanism>DIGEST-MD5</mechanis
      <mechanism>X-OAUTH2</mechanism>
      <mechanism>SCRAM-SHA-1</mechanism>
    </mechanisms>
</stream:features>

5 status
wait_for_feature_request

6 s-send challenge

<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
 bm9uY2U9IjE2OTk1NTY0ODUwMjUxOTE3NTUzIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz
</challenge>

7 c-status
wait_for_sasl_response(StateData#state.authenticated==false)

8 s-send sasl success

<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>

9 c-status
wait_for_stream(StateData#state.authenticated==true)

10 c-send header(重新发送初始化流)

<?xml version='1.0'?>
<stream:stream version='1.0' 
    xml:lang='en' 
    from='localhost' 
    xmlns='jabber:client'>

11 s-send header

<?xml version='1.0'?>
<stream:stream id='14207120876018505950' 
    version='1.0' 
    xmlns:stream='http://etherx.jabber.org/streams
    from='localhost' xmlns='jabber:client'>

12 s-send feature

<stream:features>
    <c ver='LSc28EBBWo2uA2P3nRDU+sBlbsc=' 
      node='http://www.process-one.net/en/ejabberd/' 
      xmlns='http://jabber.org/protocol/caps'/>
    <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
    <session xmlns='urn:ietf:params:xml:ns:xmpp-session'>
      <optional/>
    </session>
    <sm xmlns='urn:xmpp:sm:3'/>
</stream:features>

13 c-status
wait_for_bind

14 c-send bind

<iq type='set' id='bind_1'>
  <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
    <resource>Psi+</resource>
  </bind>
</iq>

15 s-send bind success

<iq type='result' id='bind_1'>
  <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
    <jid>admin@localhost/Psi+</jid>
  </bind>
</iq>

16 c-send iq

<iq id="aabaa" type="set">
     <session xmlns="urn:ietf:params:xml:ns:XMPP-session"/>
</iq>

17 s-send(过程会调用ejabberd_sm:open_session函数建立会话)

<iq type='result' id='aabaa'/>

18 c-status
session_established

:status变化=>gen_tcp:send_event/2

会话连接建立成功后,客户端发送普通的通信消息<iq>,<presence>,<message> 信息节,皆通过session_established2/2 函数处理

-spec session_established2(xmpp_element(), state()) -> fsm_next().
%%% Process packets sent by user (coming from user on c2s XMPP connection)
session_established2(Pkt, StateData) ->
    User = StateData#state.user,
    Server = StateData#state.server,
    FromJID = StateData#state.jid,
    ToJID = case xmpp:get_to(Pkt) of
        undefined -> jid:make(User, Server, <<"">>);
        J -> J
        end,
    Lang = case xmpp:get_lang(Pkt) of
           <<"">> -> StateData#state.lang;
           L -> L
       end,
    NewPkt = xmpp:set_lang(Pkt, Lang),
    %%%根据消息节类型,区别处理
    NewState =
    case NewPkt of
        #presence{} ->
        %%%更新出席信息
        Presence0 = ejabberd_hooks:run_fold(
                  c2s_update_presence, Server, NewPkt,
                  [User, Server]),
       %%%返回客户端结果
        Presence = ejabberd_hooks:run_fold(
                 user_send_packet, Server, Presence0,
                 [StateData, FromJID, ToJID]),
        case ToJID of
            #jid{user = User, server = Server, resource = <<"">>} ->
            ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)",
                   [FromJID, Presence, StateData]),
            presence_update(FromJID, Presence,
                    StateData);
            _ ->
            presence_track(FromJID, ToJID, Presence,
                       StateData)
        end;
        %%%iq分为get/set两种类型
        %%%get 例获取花名册
        %%%set 例注册
        #iq{type = T, sub_els = [El]} when T == set; T == get ->
        NS = xmpp:get_ns(El),
        if NS == ?NS_BLOCKING; NS == ?NS_PRIVACY ->
            IQ = xmpp:set_from_to(Pkt, FromJID, ToJID),
            process_privacy_iq(IQ, StateData);
           NS == ?NS_SESSION ->
            Res = xmpp:make_iq_result(Pkt),
            send_stanza(StateData, Res);
           true ->
            NewPkt0 = ejabberd_hooks:run_fold(
                    user_send_packet, Server, NewPkt,
                    [StateData, FromJID, ToJID]),
            check_privacy_route(FromJID, StateData, FromJID,
                        ToJID, NewPkt0)
        end;
        %%%处理message消息节
        _ ->
        NewPkt0 = ejabberd_hooks:run_fold(
                user_send_packet, Server, NewPkt,
                [StateData, FromJID, ToJID]),
        check_privacy_route(FromJID, StateData, FromJID,
                    ToJID, NewPkt0)
    end,
    ejabberd_hooks:run(c2s_loop_debug,
               [{xmlstreamelement, Pkt}]),
    fsm_next_state(session_established, NewState).

附1:ejabberd_hook模块作为系统hook(钩子),注册一系列方法在ets表hook中用于调用,以为user_send_packet例,存储格式如下

{{user_send_packet,<<"localhost">>},
 [{75,mod_caps,user_send_packet},{89,mod_carboncopy,user_send_packet}]}

调用方式(在发生某个event 的时候,run相对应的hook,从而执行该hook对应的所有actions)
1 run(Hook, Host, Args) 根据hook名字依次调用,不管每次的调用结果
2 run_fold(Hook, Host, Val, Args) 根据hook名字依次调用,每次的调用结果下次继续使用
附2:
消息层次TCP->TLS->SASL->XMPP(配置tls)
TLS传输秘钥给客户端,再使用此秘钥按照SASL协议进行用户名/密码等验证

 类似资料: