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协议进行用户名/密码等验证