erlang——cowboy与http推送

1. 相关知识简介

基于HTTP开发的程序,要让客户端较实时的感知后台变化的数据,通常有几种方式:客户端轮询、长轮询、长连接。

客户端轮询是指客户端定时向服务器发送Ajax请求,服务器收到请求后马上返回响应结果。这种方式又称为短轮询。

长轮询是指客户端向服务器发送Ajax请求,服务器收到请求后不立即返回,直到有新消息或者有数据变化时,才将响应结果发送给客户端。客户端收到响应结果后,再次向服务器发送一个请求。

对于tcp连接,可能是一次请求完成后关闭然后重新建立一个新的连接,也可以是增加keep-alive属性,保证tcp连接时不断开的。

长连接是指在页面里嵌入一个隐藏iframe,将这个隐藏iframe的src属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。

2. cowboy与http推送

客户端轮询

服务端代码:

Dispatch = 
    cowboy_router:compile([{'_',
                            [{"/",cowboy_static,{priv_file,demo,"index.html"}},
                             {"/push", login_handler, []},
                             {"/js/[…]",cowboy_static,{priv_file,demo,"js"}}
                            ]
                           }]),
{ok, _} = cowboy:start_http(http, 100, [{port,8080}],[{env,[{dispatch,Dispatch}]}]).

%login_handler.erl
init(_Type, Req, []) ->
{ok, Req, Undefined}.

handle(Req, State) ->
{{Year, Month, Day}, {Hour,Min,Sec}} = calendar:now_to_local_time(erlang:now()),
Data = lists:concat([integer_to_list(Year), "-",
                integer_to_list(Month), "-",
                integer_to_list(Day), " ",
                integer_to_list(Hour), ":",
                integer_to_list(Min), ":",
                integer_to_list(Sec)]),
{ok, Req2} = cowboy_req:reply(200, [{<<"content-type">>, <<"text/plain">>}],
                           list_to_binary(Data), Req),
{ok, Req2, State}.

前端代码:

<!DOCTYPE html>
<html>
    <head runat="server">
        <script type="text/javascript" src="/js/jquery-1.10.2.js"></script>
        <script type="text/javascript">
        $(document).ready(function longPolling(){
            $.ajax({
                type:"POST",
                url:"push",
                timeout:30000,
                complete:function(XMLHttpRequest, textStatus){
                    if(XMLHttpRequest.readyState=="4"){
                        document.getElementById('state').innerHTML = 
                            document.getElementById('state').innerHTML +
                            XMLHttpRequest.responseText + "<br/>";
                            longPolling();
                    }
                },
                error:function(XMLHttpRequest, textStatus, errorThrow){
                    if(textStatus=="timeout")
                        longPolling();
                }
            });
        });
        </script>
    </head>
    <body>
        <div id="state"></div>
    </body>
</html>

这个程序简单的模拟了客户端轮询的方式。服务端收到客户端的请求后回复内容为当前的时间,浏览器侧收到回复后则再发送同样的http请求。

============================================================

长轮询
Dispatch = 
    cowboy_router:compile([{'_',
                            [{"/",cowboy_static,{priv_file,demo,"index.html"}},
                             {"/push", longpoll_handler, []},
                             {"/js/[…]",cowboy_static,{priv_file,demo,"js"}}
                            ]
                           }]),
{ok, _} = cowboy:start_http(http, 100, [{port,8080}],[{env,[{dispatch,Dispatch}]}]).

%longpoll_handler.erl
init(_Type, Req, State) ->
    erlang:send_after(5000, self(), {message, “response”}),
    {loop, Req, State}.

info(_Msg, Req, State) ->
Data = lists:concat([integer_to_list(Year), "-",
                integer_to_list(Month), "-",
                integer_to_list(Day), " ",
                integer_to_list(Hour), ":",
                integer_to_list(Min), ":",
                integer_to_list(Sec)]),
{ok, Req2} = cowboy_req:reply(200, [{<<"content-type">>, <<"text/plain">>}],
                           list_to_binary(Data), Req),
{ok, Req2, State}.

与前面例子不同的是,init函数中设置了一个5s的定时器,并且返回为loop。等定时器超时后,将当前时间作为内容回复给客户端。这也就是对客户端的请求hold住一段时间,然后再进行回复。

===========================================================

Websocket

WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。为了建立一个Websocket连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息,其中附加头信息"Upgrade:WebSocket"表明这是一个申请协议升级的HTTP请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的WebSocket连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

对cowboy提供的websocket的example抓包分析,可看出tcp连接建立后,在单个tcp连接上服务端不断的给客户端推送消息,而不需要客户端发送请求。对比长轮询的抓包情况,服务端要推送数据必须要有客户端发送请求才行。因此,websocket是一种更为高效的推送方式。

websocket抓包图:


长轮询抓包图:


========================================================

cowboy里的一些细节

keep-alive:

通常服务端对客户端请求的响应中没有显示指定Connection:keep-alive的情况下,默认是响应回复后断开tcp连接。然而cowboy不会立即断开客户端的tcp连接,而是维持一段时间。具体时长为:单个tcp连接上处理客户端的请求累计达到指定的次数后,断开tcp连接,默认为100次。前面的例子中,都未显示设置keep-alive,因此tcp连接维持一段时间后便会断开。当然,我们可以显示的增加keep-alive属性,这样不管是客户端轮询,还是长轮询,tcp连接维持不断开的状态。

另外,不显示增加keep-alive属性时,我们可以控制tcp维持的时间,即请求累计次数。

在调用start_http函数时,传入参数{max_keepalive,Count}来控制保持keepalive前请求的个数。

{ok, _} = cowboy:start_http(http, 100, [{port, 8080}], 
                            [{env,[{dispatch,Dispatch}]},
                                   {max_keepalive,10}]).

==============================================================

cowboy中http请求处理流程

tcp连接建立后,回调进入cowboy_protocol开始http包的分析。当http的包完整解析后,依次调用中间层模块的execute方法,默认只有cowboy_router和cowboy_handler模块。cowboy_router根据请求的url找到对应的handler;cowboy_handler完成请求的处理。

在cowboy_handler中调用cowboy_router匹配找到的handler的init初始化方法,根据返回值判断是直接调用对应的handler方法,还是开始进入接收循环。有前面的例子可以知道,当返回值为带ok的元组时,直接调用handler方法进行http请求的处理,处理完成后调用terminate_request结束本次请求的处理。当返回值为带loop的元组时,开始接收循环。在循环期间,从socket上接收到的数据会累加到缓存中,当定时器超时或者收到来自其他进程的消息时,触发回调(Handler:info/3),通常在这个回调中完成http请求的处理。处理完成后调用terminate_request结束本次请求的处理。

转载于:https://my.oschina.net/hncscwc/blog/411025

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值