http的反向推送通常使用"长轮询"或"长连接"的方式。 所谓"长轮询"是指客户端发送请求给服务器,服务器发现没有数据需要发送给客户端。

http的反向推送通常使用"长轮询"或"长连接"的方式。
所谓"长轮询"是指客户端发送请求给服务器,服务器发现没有数据需要发送给客户端于是hold住不及时返回,等有数据需要发送给客户端时,进行回复,然后关闭连接,客户端收到回复后再发送新的http请求,以便服务器能有对应的请求用于消息的反向推送。
而"长连接"是在长轮询的基础上增加"keep-alive"属性,服务器收到请求后不直接回复,等有数据需要发送给客户端时再进行response,但是并不关闭连接,这样客户端收到服务器的response后在同一连接上再次发送http请求。

在ejabberd的实现中,采用了bosh技术来完成对应的工作,具体定义可参考:

英文: http://go.rritw.com/xmpp.org/extensions/xep-0124.html

中文: http://go.rritw.com/wiki.jabbercn.org/XEP-0124

大概实现原理:ejabberd收到一个客户端http请求后会为该客户端最终创建三个进程:ejabberd_http, ejabberd_http_bind, ejabberd_c2s。

ejabberd_http进程不断的从对应的socket上收客户端的请求,并转发交给对应的ejabberd_http_bind进程进行处理,然后同步等待处理结果,并将结果返回给客户端。

init() ->
...
receive_headers(State). receive_headers(#state{trail=Trail} = State) ->
SockMod = State#state.sockmod,
Socket = State#state.socket,
Data = SockMod:recv(Socket, 0, 300000),
case State#state.sockmod of
gen_tcp ->
NewState = process_header(State, Data),
case NewState#state.end_of_request of
true -> ok;
_ -> receive_headers(NewState)
end;
_ ->
case Data of
{ok, D} ->
parse_headers(State#state{trail = <<Trail/binary, D/binary>>});
{error, _} ->
ok
end
end. process_header(State, Data) ->
case Data of
...
{ok, http_eoh} ->
...
Out = process_request(State2),
send_text(State2, Out),
case State2#state.request_keepalive of
true ->
...
#state{sockmod = SockMod,
socket = Socket,
request_handlers = State#state.request_handlers};
_ ->
#state{end_of_request = true,
request_handlers = State#state.request_handlers}

从代码中可看出,未设置keep-alive属性的时候,该进程处理完一次http请求后便自己结束(长轮询模式)。设置了keep-alive属性的时候,该进程不断的循环接收http请求,并转发接收与响应(长连接模式)。

ejabberd_http_bind进程负责hold住http请求,对于正常的客户端请求,ejabberd_http_bind进程会将请求转发给对应的ejabberd_c2s进程进行实际业务的处理,而对于空的请求(便于服务器反向推送数据),ejabberd_http_bind设置定时器,等待ejabberd_c2s进程对实际请求的响应或者是需要推送给客户端的消息。

handle_sync_event({send_xml,Packet},_From,StateName,
#state{http_receiver = undefined} = StateData) ->
Output = [Packet | StateData#state.output],
Reply = ok,
{reply, Reply, StateName, StateData#state{output = Output}}; handle_sync_event({send_xml, Packet}, _From, StateName, StateData) ->
Output = [Packet | StateData#state.output],
cancel_timer(StateData#state.timer),
Timer = set_inactivity_timer(StateData#state.pause,
StateData#state.max_inactivity),
HTTPReply = {ok, Output},
gen_fsm:reply(StateData#state.http_receiver, HTTPReply),
cancel_timer(StateData#state.wait_timer),
Rid = StateData#state.rid,
ReqList = [#hbr{rid = Rid,key = StateData#state.key,
out = Output
} |
[El || El <- StateData#state.req_list,
El#hbr.rid /= Rid ]],
Reply = ok,
{reply, Reply, StateName,
StateData#state{output = [],
http_receiver = undefined,
req_list = ReqList,
wait_timer = undefined,
timer = Timer}}; handle_sync_event({http_get,Rid,Wait,Hold},From,StateName,
StateData) ->
%% setup timer
send_receiver_reply(StateData#state.http_receiver, {ok, empty}),
cancel_timer(StateData#state.wait_timer),
TNow = tnow(),
if
(Hold > 0) and
(StateData#state.output == []) and
((TNow -StateData#state.ctime<(Wait*1000*1000)) and
(StateData#state.rid == Rid) and
(StateData#state.input /= cancel) and
(StateData#state.pause == 0) ->
WaitTimer = erlang:start_timer(Wait * 1000, self(), []),
%% MR: Not sure we should cancel the state timer here.
cancel_timer(StateData#state.timer),
{next_state,StateName,
StateData#state{http_receiver = From,
wait_timer = WaitTimer,
timer = undefined}};
(StateData#state.input == cancel) ->
cancel_timer(StateData#state.timer),
Timer = set_inactivity_timer(StateData#state.pause,
StateData#state.max_inactivity),
Reply = {ok, cancel},
{reply, Reply, StateName,
StateData#state{input = queue:new(),
http_receiver = undefined,
wait_timer = undefined,
timer = Timer}};
true ->
cancel_timer(StateData#state.timer),
Timer = set_inactivity_timer(StateData#state.pause,
StateData#state.max_inactivity),
Reply = {ok, StateData#state.output},
%% save request
ReqList = [#hbr{rid = Rid,
key = StateData#state.key,
out = StateData#state.output
} |
[El || El <- StateData#state.req_list,
El#hbr.rid /= Rid]
],
{reply, Reply, StateName,
StateData#state{output = [],
http_receiver = undefined,
wait_timer = undefined,
timer = Timer,
req_list = ReqList}}
end; handle_info({timeout, WaitTimer, _}, StateName,
#state{wait_timer = WaitTimer} = StateData) ->
if
StateData#state.http_receiver /= undefined ->
cancel_timer(StateData#state.timer),
Timer = set_inactivity_timer(StateData#state.pause,
StateData#state.max_inactivity),
gen_fsm:reply(StateData#state.http_receiver, {ok, empty}),
Rid = StateData#state.rid,
ReqList = [#hbr{rid = Rid,
key = StateData#state.key,
out = []
} |
[El || El <- StateData#state.req_list,
El#hbr.rid /= Rid ]
],
{next_state, StateName,
StateData#state{http_receiver = undefined,
req_list = ReqList,
wait_timer = undefined,
timer = Timer}};
true ->
{next_state, StateName, StateData}
end;

ejabberd_http进程最终会调用ejabberd_http_bind的http_get方法获取请求的响应结果或者是需要推送的数据,ejabberd_http_bind进程收到请求后会进行相应处理,比如有数据则直接回复,或者设置定时器。当收到ejabberd_c2s进程推送过来的数据时,停止定时器并将数据立即回复给ejabberd_http进程,如果定时器超时则回复一个空的消息。
=============================
ejabberd_c2s为客户端对应的会话进程,负责维护客户端的在线状态,联系人列表,请求处理等等。
============
服务器在创建ejabberd_http_bind进程时,会生成一个唯一的sid,用于标识该进程,该sid与ejabberd_http_bind进程pid的对应关系会存储到mnesia中,同时服务器会将sid也会告诉客户端,客户端后续的请求也都需要带上该sid。http_bind收到请求后根据sid从mnesia查找匹配的ejabberd_http_bind进程。

process_request(Data, IP) ->
...
case catch parse_request(Data, PayloadSize, MaxStanzaSize) of
%% No existing session:
{ok, {"", Rid, Attrs, Payload}} ->
...
Sid = sha:sha(term_to_binary({now(), make_ref()})),
case start(XmppDomain, Sid, "", IP) of
{ok, Pid} ->
handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,Payload, PayloadSize, IP)
...
end;
{ok, {Sid, Rid, Attrs, Payload1}} ->
...
handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
StreamStart, IP); handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
Payload, PayloadSize, IP) ->
...
mnesia:dirty_write(#http_bind{id = Sid,
pid = Pid,
to = {XmppDomain,XmppVersion},
hold = Hold,
wait = Wait,
process_delay = Pdelay,
version = Version}),
handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, true, IP). http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
case mnesia:dirty_read({http_bind, Sid}) of
[] ->
{error, not_exists};
[#http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess] ->
...
{gen_fsm:sync_send_all_state_event(FsmRef,
#http_put{rid = Rid, attrs = Attrs,
payload = Payload,
payload_size = PayloadSize, hold = Hold,
stream = NewStream, ip = IP}, 30000), Sess}
end.
ejabberd_http_bind与ejabberd_c2s会互相记住对方的进程pid,这样每个客户端都有自己唯一的ejabberd_http_bind与ejabberd_c2s进程进行相应的请求处理。【转】 http://my.oschina.net/hncscwc/blog/178471

ejabberd中的http反向推送的更多相关文章

  1. IOS中程序如何进行推送消息(本地推送,远程推送)

    [1]-------------什么是推送消息? 我就以一张图解释------------ [2]-----------IOS程序中如何进行本地推送?----------- 2.1,先征求用户同意 1 ...

  2. MVC 中使用 SignalR 实现推送功能

    MVC 中使用 SignalR 实现推送功能 一,简介 Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Pus ...

  3. 在 Asp.NET MVC 中使用 SignalR 实现推送功能 [转]

    在 Asp.NET MVC 中使用 SignalR 实现推送功能 罗朝辉 ( http://blog.csdn.net/kesalin ) CC许可,转载请注明出处 一,简介 Signal 是微软支持 ...

  4. Asp.NET MVC 中使用 SignalR 实现推送功能

    一,简介Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请 ...

  5. 在 Asp.NET MVC 中使用 SignalR 实现推送功能

    一,简介Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请 ...

  6. SignalR 中丰富多彩的消息推送方式

    在上一篇 SignalR 文章中,演示了如何通过 SignalR 实现了简单的聊天室功能:本着简洁就是美的原则,这一篇我们也来聊聊在 SignalR 中的用户和组的概念,理解这些基础知识有助于更好的开 ...

  7. asp.net 中长尾链接实现推送 -- comet

    一般需求推送服务时,都会去第三方拿推送组件,如”极光“,”百度“,”小米"什么的,自己用.net实现推送服务端需要面对很多问题,比如C10K,但是企业内部使用往往用不了10K的链接,有个1K ...

  8. django中实现微信消息推送

    -公众号(不能主动给用户发消息) -认证的公众号:需要营业执照,需要交钱,可以发多篇文章 -未认证的公众号:一天只能发一篇文章 -服务号(微信推送) -需要申请,需要认证 -可以主动给用户推送消息 - ...

  9. 企业运维实践-丢弃手中的 docker build , 使用Kaniko直接在Kubernetes集群或Containerd环境中快速进行构建推送容器镜像

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 本章目录 目录 首发地址: h ...

随机推荐

  1. 自学JAVA总结

    2.在定义常量的时候C语言中定义为const而JAVA中为final3.在JAVA声明成员变量的时候,使用static来定义.4.在JAVA中的boolean类型只包括true和false,但是在C中 ...

  2. XAML系列学习

    在XAML中为属性赋值 1.使用Attribute=value形式 <Rectangle Width="100" Height="100" Stroke= ...

  3. X86 复制本地 生成有问题、类型初始值设定项引发异常

    一. 选择项目,右击属性——生成——目标平台 选择x86就可以了. 二. 有的时候你发现你项目中的dll没有生成到本地bin,这时右击它属性,到它引用的地方复制引用dll放到部署环境中,你会发现一样报 ...

  4. 一步步学习NHibernate(9)——连接查询和子查询(1)

    请注明转载地址:http://www.cnblogs.com/arhat 在前几章中,我们把HQL的基本查询学习了一下,但是只有基本查询很显然不能满足我们的需求,那么就需要一下复杂查询比如" ...

  5. TWaver3D入门探索——3D拓扑图之人在江湖

    俗话说,有人的地方就有江湖,江湖就是帮派林立错综复杂的关系网.今天我们就来展示这样一个小小的江湖. 故事背景 崇祯末年,民不聊生,烽烟四起-- 江湖都是有背景的,我们的3D江湖也需要一个背景.江湖就是 ...

  6. [原创] zabbix学习之旅一:源码安装

    zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案.zabbix能监视各种网络参数,保证服务器系统的安全运营:并提供灵活的通知机制以让系统管理员快速定位/解决存 ...

  7. 1198: [HNOI2006]军机调度 - BZOJ

    Description 凯萨拥有一支由n个人组成的雇佣军,他们靠在威尼斯商行接任务过活.这支军队的成份比较复杂,不同的人往往具有不同的技能,有的人还拥有多项技能.威尼斯商行的任务也参差不齐,有的需要几 ...

  8. 引用外部CSS的link和import方式的分析与比较

    很多网页中的 CSS 链接与引用是这样写的: <style type="text/css" media="screen"> @import url( ...

  9. AForm

    相信大部分程序员都接触过表单,表单是收集用户输入的不二之选,但是表单的开发又是最繁琐.最复杂的,简单地说,开发表单你需要涉及到很多知识: 布局,表单如何布局排版,看起来最清晰整洁,且符合用户体验 控件 ...

  10. DX 的.x 文件

    template Header { <3D82AB43-62DA-11cf-AB39-0020AF71E433> WORD major; WORD minor; DWORD flags;} ...