Erlang cowboy websocket 服务器

时间:2022-06-01 19:24:41

Erlang cowboy websocket 服务器

原文见于:

http://marcelog.github.io/articles/erlang_websocket_server_cowboy_tutorial.html

本文不是原文的简单翻译,是参考原文,根据我的理解和实践写出来的。本文的源码见于:

https://github.com/marcelog/erws

1 引言

Erlang可以用来实现一个websocket服务器。cowboy这样框架可以完成这个任务,使我们不必关注websocket协议的细节。在吃正餐之前,我们先来点围碟:

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebSocket通信协议于2011年被IETF定为标准 RFC 6455,WebSocketAPI被W3C定为标准。在WebSocket API中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

现在,很多网站为了实现即时通讯(real-time),所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(time interval)(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request d的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求(request),然而HTTP request 的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽。

而最比较新的技术去做轮询的效果是长轮询(long polling),这是很古老的技术。长轮询的优势在于,它完全颠覆了典型的客户端发送请求的做法。长轮询的核心在于客户端和服务器间的相互联系,打破了客户端尽快发送请求的传统惯例。客户端期望服务器能够长时间留住请求,保持潜在的TCP连接的开放性。只要服务器拥有希望同客户端共享的信息,它就会将数据传送给客户端并终止连接。这种做法的效率相对较高,免除了所有HTTP请求的麻烦。客户端收到更新数据后马上向服务器发送下个请求,依然期望服务器留住该请求,直到有需要传回的数据为止。

基于HTTP的长连接,是一种通过长轮询方式实现"服务器推"的技术,它弥补了HTTP简单的请求应答模式的不足,极大地增强了程序的实时性和交互性。长连接、长轮询一般应用与WebIM、ChatRoom和一些需要及时交互的网站应用中。其真实案例有:WebQQ、Hi网页版、Facebook IM等。

而在 WebSocket API,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即使服务带来了两大好处:

Header,互相沟通的Header是很小的-大概只有 2 Bytes
Server Push,服务器可以主动传送数据给客户端。
因此WebSocket是一种更新更优秀的技术。

2 为什么用cowboy

cowboy是终极悍将。到目前为止(2012-05),可用来写websocket服务的erlang工具有:

Socket.io-Erlang

这是最有吸引力的方法,因为可以用在客户端。Socket.io非常受欢迎,特别在NodeJS里,因为其中一个作者(Frederic Trottier-Hebert)也是LearnYouSomeErlang的作者。可是自从socket.io 0.6.x之后,作者就不再添加新功能。所以对这个产品非常不利。

MochiWeb

非常流行的HTTP框架,但是本身不支持websocket。

Yaws

这个是全功能的 HTTP server,足够健壮性、可扩展性 。适用于静态和动态页面,也支持websockets。但是对我的需求来说,有点杀鸡用牛刀。当然,如果没有其他方案好用,我肯定会选择这个。(译者注:Yaws也是我下一个要学习使用的产品)

Misultin

原作者表示不再更新,已经转向mochiweb和cowboy。

Cowboy

非常有意思的东西。HTTP是这个产品框架的一部分。这个东西更像一个连接池,我们可以设置处理TCP或SSL连接。最大的缺点是缺乏文档,出了问题有时候需要看源代码。

3 开始编程

原文使用了rebar来构建项目。Rebar是一款Erlang的构建工具,使用它可以方便的编译、测试erlang程序、内联驱动和打包Erlang发行版本。

【译者:我继续使用上一篇文章的make工具(Erlang cowboy入门参考),因此,本文与原文略有出入】

这次,我们创建一个erws(Erlang WebSocket),主要的模块就是erws_handler.erl,所有必要的文件都会在下面展示。

当程序运行,cowboy会监听8080端口,分发HTTP请求到erws_handler。

erws_handler实现了2个处理器:cowboy_http_handler和cowboy_websocket_handler。第一个处理器接受普通的HTTP GET请求,并启动websocket完成握手,这个过程通过发送必要的头信息使HTTP连接升级(upgrade),如协议所述。对我们来讲,这个过程非常简单,我们只需要对erws_handler:init/1(参考代码)返回一个元组。所有脏活都是由cowboy的模块cowboy_http_websocket来完成。

一旦这个过程完成,通过调用cowboy_http_websocket_handler暴露出来的api,我们就可以把收到的请求当作消息处理,和普通的gen_server非常类似。

创建项目

$ mkdir erws && cd erws
$ wget --no-check-certificate https://raw.githubusercontent.com/ninenines/erlang.mk/master/erlang.mk
$ make -f erlang.mk bootstrap bootstrap-rel
$ make

erws_app.src

注意:一定要加入"crypto"模块,否则websocket连接无法建立。crypto在websocket连接握手时被cowboy用到。

erws_app.src 资源文件用来生成程序的描述,全文如下:

{application, erws, [
	{description, "Erlang WebSocket Demo By cheungmine"},
	{vsn, "1"},
	{modules, []},
	{registered, []},
	{applications, [
		kernel,
		stdlib,
		crypto,
		cowboy,
		compiler,
		syntax_tools
	]},
	{mod, {erws_app, []}},
	{env, []}
]}.

Makefile

内容如下:

PROJECT = erws
DEPS = cowboy
include erlang.mk

erws_app.erl

这个代码设置cowboy监听并接受连接。然后分发请求到指定模块。代码全文如下:

-module(erws_app).
-behaviour(application).

-export([start/2]).
-export([stop/1]).

start(_Type, _Args) ->
	Dispatch = cowboy_router:compile([
		{'_', [
			{"/", cowboy_static, {priv_file, erws, "index.html"}},
			{"/websocket", erws_handler, []}
		]}
    ]),
	{ok, _} = cowboy:start_http(http, 100, [{port, 8080}],
		[{env, [{dispatch, Dispatch}]}]),
	erws_sup:start_link().

stop(_State) ->
	ok.

erws_sup.erl

督程代码。注意最后一行:

-module(erws_sup).
-behaviour(supervisor).

-export([start_link/0]).
-export([init/1]).

start_link() ->
	supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
	Procs = [],
	{ok, {{one_for_one, 5, 10}, Procs}}.

erws_handler.erl

生成主模块代码:

$ make new t=cowboy_http n=erws_handler

全文如下:

-module(erws_handler).
-behaviour(cowboy_http_handler).
-behaviour(cowboy_websocket_handler).
-export([init/3, handle/2, terminate/3]).
-export([
	websocket_init/3, websocket_handle/3,
	websocket_info/3, websocket_terminate/3
]).

init({tcp, http}, _Req, _Opts) ->
	{upgrade, protocol, cowboy_websocket}.

handle(_, State) ->
	{ok, Req2} = cowboy_http_req:reply(404, [{'Content-Type', <<"text/html">>}]),
	{ok, Req2, State}.

websocket_init(_TransportName, Req, _Opts) ->
	{ok, Req, undefined_state}.

websocket_handle({text, Msg}, Req, State) ->
	{reply, {text, << "responding to ", Msg/binary >>}, Req, State, hibernate };

websocket_handle(_Any, Req, State) ->
	{reply, {text, << "whut?">>}, Req, State, hibernate }.

websocket_info({timeout, _Ref, Msg}, Req, State) ->
	{reply, {text, Msg}, Req, State};

websocket_info(_Info, Req, State) ->
	lager:debug("websocket info"),
	{ok, Req, State, hibernate}.

websocket_terminate(_Reason, _Req, _State) ->
	ok.

terminate(_Reason, _Req, _State) ->
	ok.

index.html

这是WebSocket的客户端。使用支持WebSocket的浏览器访问链接:http://your-webserver-ip:8080/

如何开发这样的WebSocket页面,参考:http://dev.w3.org/html5/websockets/

erws/priv/index.html的全部内容:

<!DOCTYPE HTML>
<html>
<head>
    <script type="text/javascript">
        var ws = new Object;

        function send() {
            ws.send("hi, cowboy websocket by cheungmine!");
            console.log('sent');
        }

        function open() {
            if (!("WebSocket" in window)) {
                alert("WebSocket NOT supported by your Browser!");
                return;
            }

            console.log('open');
            ws = new WebSocket("ws://"+window.location.host+"/websocket");
            ws.onopen = function() {
                console.log('connected');
            };

            ws.onmessage = function (evt) {
                var received_msg = evt.data;
                console.log("Received: " + received_msg);
                var txt = document.createTextNode("Got from server: " + received_msg);
                document.getElementById('messages').appendChild(txt);
            };

            ws.onclose = function() {
                // websocket is closed.
                console.log('close');
            };
        }
    </script>
</head>
<body>
<div id="sse">
   <a href="javascript:open()">Press Me to Open WebSocket</a><br/>
   <a href="javascript:send()">Press Me to Send hi Message</a>
</div>
<div id="messages">
</div>
</body>
</html>

4 结论

以上就是全部代码,你可以获取全部源代码:

https://github.com/marcelog/erws

译者注:本文没有采用rebar,你可以参考:http://blog.csdn.net/ubuntu64fan/article/details/40542549

编译和运行:

$ make
$ ./_rel/erws_release/bin/erws_release console

显示如下:

(erws@127.0.0.1)1>

输入q().可以退出服务。

打开FireFox浏览器,地址栏输入:http://192.168.82.181:8080/,显示如下:

Erlang cowboy websocket 服务器

此时按Send hi Message,页面没有任何反应。先点Open WebSocket,再点Send hi,结果如下:

Erlang cowboy websocket 服务器

完全正确

尽管我也不怎么喜欢代码中依赖别人提供的框架,但是cowboy确实干的不错,我认为cowboy是构造复杂应用不错的选择。不仅仅是支持websocket,同时还有HTTP和HTTPS。

最重要的是,cowboy让你关注业务而不是技术实现细节,比如构建连接池,websocket协议等。仅仅几行代码,我们就可以给沉闷乏味的web程序带来魔力。

Erlang cowboy websocket 服务器的更多相关文章

  1. Cowboy http服务器 websocket

    一.基础介绍 cowboy是一个小巧.快速.模块化的http服务器,采用Erlang开发.其中良好的clean module使得我们可以扩展到多种网络协议之中,cowboy自带的有tcp和ssl,而也 ...

  2. Erlang cowboy 入门参考之现代Web的发展历史

    Erlang cowboy 入门参考之现代Web发展史 原文: http://ninenines.eu/docs/en/cowboy/1.0/guide/modern_web/ 让我回顾一下web技术 ...

  3. Erlang cowboy http request生命周期

    Erlang cowboy http request生命周期 翻译自: http://ninenines.eu/docs/en/cowboy/1.0/guide/http_req_life/ requ ...

  4. Erlang cowboy 入门参考

    Erlang cowboy 入门参考 cheungmine,2014-10-28 本文翻译自: http://ninenines.eu/docs/en/cowboy/HEAD/guide/gettin ...

  5. HTML5学习总结-08 WebSocket 服务器推送

    一 WebSocket 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展 ...

  6. python实现websocket服务器,可以在web实时显示远程服务器日志

    一.开始的话 使用python简单的实现websocket服务器,可以在浏览器上实时显示远程服务器的日志信息. 之前做了一个web版的发布系统,但没实现在线看日志,每次发布版本后,都需要登录到服务器上 ...

  7. Erlang cowboy 处理不规范的client

    Erlang cowboy 处理不规范的client Cowboy 1.0 參考 本章: Dealing with broken clients 存在很多HTTP协议的实现版本号. 很多广泛使用的cl ...

  8. 根据Unix哲学来编写你的HTML5 Websocket服务器来实现全双工通信

    websocketd代表WebSocket的守护进程 websocketd处理的是浏览器和服务器之间的WebSocket连接,它会启动你所指定的服务器端应用来对WebSockets进行处理,然后在浏览 ...

  9. 实现一个websocket服务器-理论篇

    本文是Writing WebSocket servers的中文文档,翻译自MDNWriting WebSocket servers.篇幅略长,个人能力有限难免有所错误,抛砖引玉共同进步. websoc ...

随机推荐

  1. XAML 概述四

    这一节我们来简单介绍一下XAML的加载和编译,它包括如下三种方式:  · 只使用代码  · 使用代码和未编译的XAML  · 使用代码和编译过的BAML 一. 只使用代码 我们首先创建一个简单的控制台 ...

  2. &lbrack;Google Code Jam &lpar;Round 1A 2008&rpar; &rsqb; A&period; Minimum Scalar Product

    Problem A. Minimum Scalar Product   This contest is open for practice. You can try every problem as ...

  3. 多线程编程学习笔记——使用异步IO(一)

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  4. SQL优化总结之二

    1.列优先 如图有表A和表B,对其查询时,会有如下语句: select a.*,b.* from a,b where a.id = b.a_id; 注意from 后边的表名, a.如果多表查询是完全无 ...

  5. SpringBoot打包成war

    关于SpringBoot打成jar包以及jar包如何在Linux持久运行,我在前面已经说过了,所以本次不再赘述. 关于SpringBoot打包成war,其实步骤特别简单,如下图所示(如果是jar,通常 ...

  6. JavaScript的屏幕输出及时间函数

    Js屏幕输出 重定向页面输出: 更改输出流,如果在加载时使用则会正常排序,如果在HTML加载完成后使用,如点击事件,则会全部覆盖原HTML的内容. 1 <script> 2 //参数为输出 ...

  7. Angular2入门:TypeScript的类型 - 类型、null、undefined

  8. input标签(按钮)

    按钮: <input type="button" name="..." value="..." /> <input typ ...

  9. js三级联动

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  10. selenium下拉框踩坑埋坑

    本文来自网易云社区 作者:王利蓉 最近web端全站重构,所有的页面都大大小小都有些变动,UI就全军覆没了,用例从登录改,改到个人信息页面发现根以前的实现方式完全不一样,这可怎么解决 1.以前的实现(o ...