2014年3月21日星期五

[]eclipse开发erlang

本文自动转发自我的博客: http://www.haofengjing.org/2518.html

一、相关资料

Erlang 的官方网站是http://www.erlang.org.其左侧的连接指出了我们可以从这里获取的资源。

image

其中,

Downloads 连接可以下载到所有版本的Erlang.

Links 是一些其它资源的链接

Enhancements 是一些Erlang的增强功能

Getting Started 是教学

Documentation 文档,相当于帮助文件

Examples 示例

二、安装

以Windows下安装为例。下载最新 Windows binary 版本。如:R14B01。下载后文件名为 otp_win32_R14B01.exe 直接安装即可。安装后的路径设置,这步很重要!

方法一、

把bin (如 C:\erl5.8.2\bin)添加到系统变量的Path后(右键我的电脑/属性/高级/环境变量)。

方法二、

在环境变量里增加 EL_PATHS 值为 对应的 bin 路径。然后把 %EL_PATHS% 添加到 Path里。这种做法会在eclipse中使用erlang时自动在方法一的基础上完成。

三、检查Erlang是否安装成功

打开 cmd ,输入 erl 后回车,如果看到如下的信息,表明安装成功。

image

四、使用 eclipse

eclipse 开发 erlang 的要求

1、eclipse版本 3.5.0 至 3.5.2

2、jdk 1.5

3、erlide

Ok,先安装 jdk 1.5 ,运行解压后的 eclipse (最好是只含java的版本),然后在 Help / Install New Software 中使用 http://erlide.org/update 进行更新。在选项列表中选中

image

就可以了。安装后重启eclipse。完成!

五、第一个 erlang Hello World!

1、打开 eclipse , New / Project / Erlang Project

image

输入 Project Name "HelloWorld",点击 Finish

eclipse 会自动创建三个文件夹

image

其中:

ebin 存放编译之后的二进制文件(扩展名为beam)

include 存放程序中所引用到的文件

src  存放源码文件

2、右击 src ,选 New Module 来新建一个erlang文件(其扩展名为erl)

在Module Name 输入"hw"。注意,这里的名称要小写的英文或下划线。点击 Finish

3、编写print函数

输入以下代码

printHelloWorld() -> 
    io:format("Hello World!~n").

OK,接下来把printHelloWorld写入到-export里,这样就可以把函数导出了。如:-export([printHelloWorld/0]).  这里的0表示没有参数。

说明:函数名称要求第一个字母小写,大写被示为变量。函数的一般格式为

方法名称(参数) –>

    方法体.(注意这里的结束符为一个英文件的句点)

3、编译

右击hw.erl,选择 Run As / Run Configurations…

双击 Erlang application 会产生一个新的配置

image

在右侧选择你的工程

image

在Runtimes下输入一个Node Name

image

之后点击 Apply,Run就可以运行了

这时在控制台 Console 里就会出现

image

编译命令为 c(文件名).

我们输入c(hw).之后回车

image

这时提示一个错误:没有对应的文件。

使用pwd().查看当前的路径为

image

可见,hw.erl所在目录src没有被识别。所以我们使用cd命令转到这个目录

image

之后就可以编译了

image

编译后在ebin目录下会产生相应的hw.beam文件。运行如下

image

OK,第一个HelloWorld程序完成了。

六、推荐书籍

《Erlang 程序设计》即英文版的

image

七、帮助文件

Erlang的帮助文件可以在 doc 目录(如:C:\erl5.8.2\doc)下找到。


2014年3月20日星期四

[]UPYUN:用Erlang开发的对象存储系统

本文自动转发自我的博客: http://www.haofengjing.org/2517.html

在国内的几家云计算创业公司当中,UPYUN(又拍云)选择了一个比较独特的定位:面向开发者提供非结构化数据云存储服务。非结构化数据存储服务一个很重要的卖点是要提供快速的静态文件访问能力,这对底层的存储系统性能和上层的CDN系统提出了较高的要求。

黄慧攀@oneoo)是UPYUN技术总监。在QCon上海2013大会上,黄慧攀介绍了UPYUN的CDN系统架构,包括Nginx的二次开发经验、防盗链服务的实现、海量小文件的性能处理等;在QCon北京2014大会上,他将对UPYUN底层的对象存储系统的研发经验进行分享。

在本次采访中,黄慧攀介绍了UPYUN对象存储系统的一些历史,团队的分工,以及做测试方面的一些思路。

InfoQ:先介绍一下你自己吧。你关于计算机的知识都是自学,从底层网络、操作系统到上层的Java、PHP都玩,Lua也玩。你对技术的选择有什么标准吗?如果有,是怎样的标准?

黄慧攀:我是出身于广东一个小城市"鹤山"人,最早是在95年接触电脑,98年开始使用互联网,那时网易还只是做邮箱服务的,我非常感谢我的初中母校,使我能这么早期接触到互联网,影响一生 :) 也因为当年电脑、互联网才刚刚起步,学校也缺乏较好的教育能力,所以很多知识需要自学。也因为这个兴趣太浓,搞得其他学科基本都挂科了,也就没考上高中和大学。到现在还是有点小后悔,起码得把英文学好。

2001年,18岁的我第一份工作是市里一个集团公司的B2B门户网站,负责程序开发工作。那时用的语言是PHP,边学边做的折腾了3年时间。

2004年,项目因为市场、资金的原因结束了。在我们的小城市互联网就业机会基本为零,只好转到一个做弱电工程的公司任技术工程师,负责网络系统方案设计、智能灯光系统等等。

2006年,压抑不住互联网的心,就出来创办 yo2.cn 优博网,国内第一个基于 WordPress 的博客服务平台,这个创业经历使我的技术能力提升很大,因为没人嘛,所以整个网站的事情都得自己做,开发、运维、客服,甚至设计等等。记得当时网站被人吐槽最多的就是用户体验,我想如果能把这块也做好,可以做UED了,哈哈。

2009年,机缘巧合来到杭州,跟朋友做了几次创业,虽然也是失败告终,但在其中的过程使自己成长了很多,因为创业嘛,所以很多事情都必须自己做的,这就奠定了我的技术层面比较广的基础。

2011年,收到又拍网的邀请,开始又拍云的开发工作至今。

经历这么多年和多次创业,积累到比较丰富的技术经验。知识比较全面,在看待技术选型方面的把握还是比较准的。比如:Java的优点是适合大型项目、团队协作开发,缺点也很明显,开发周期长、人员成本相对PHP高一些;而PHP的优点则是适合中小型项目、开发周期短、人员成本低,当然弊端也很明显,不支持多线程、系统资源占用高。每个语言都有自己的优缺点,要根据项目实际情况来选择。后来一个偶然的机会接触到Lua语言,发现它跟PHP很像,但又没了PHP几个大缺点,非常棒。所以现在我主要使用 C 和 Lua 这两个开发语言。

InfoQ:你自己做过博客平台,也在企业网站、网游等网站做过,现在在UPYUN,可以说是从面向消费者的.com公司转移到了一个更加基础一些的服务。你觉得在UPYUN做的事情跟以前有什么不一样吗?

黄慧攀:我觉得做UPYUN这件事,是之前几个项目的升华吧。因为这些面向消费者的项目让我知道在开发过程中产生的痛点,从而挖掘出开发者的需求。我很高兴能为开发者服务,帮助大家更快的把项目做好。

InfoQ:能不能简单介绍一下UPYUN这套对象存储系统的研发历程?比如是什么时候开始做的,最初的设计者是什么背景,借鉴过哪些思路,研发的过程中有没有什么好玩的故事等等。

黄慧攀:UPYUN的对象存储系统其实早在08年就开始设计的,当初用的是 MogileFS,为又拍网服务。因为早期的MogileFS的设计本身有一定限制,tracker角色的元信息使用单个MySQL实例存储,无法满足我们日益增长的存储量,所以在2010年转为使用Erlang语言开发。设计目标是提供PB级别的存储服务,经历1年多的业务测试才正式对外开放存储服务。

选择Erlang语言进行开发,主要是语言本身就支持分布式,这可以节省很多开发工作。且Erlang语言在电信行业的应用非常广泛,稳定性有保障。

在分布式算法的选型上是参考Dynamo方案。而在具体的数据存储结构方面则是自主研发的一致性哈希算法,以实现多机柜、多服务器和多磁盘之间的数据备份工作。做到每文件的对应备份点在不同机柜、不同服务器上,避免某台服务器甚至某个机柜的服务器宕机而影响到文件的读写操作。

至于测试周期长达1年多,是因我们本身又拍网(照片社区)的数据量就非常庞大,从老的MogileFS集群迁移到新的云存储服务器占大部分时间,另外是因分布式存储服务的容灾测试过程比起应用测试要漫长得多,主要的测试点会有:某磁盘故障、某服务器故障、某机柜故障等好几种灾难测试,且每个故障都会产生一定量的数据迁移,文件会在集群内部自动寻找合适的备份点再建备份,所以说测试周期需要很长时间。也只有做到充分的测试,我们才放心的在集群上存储大量数据。否则等遇到无法排除的问题,要考虑新建集群的话,迁移成本和周期都会非常巨大。比如10PB的数据要从A集群迁移到N集群,网络传输就要100pb,基于10gb网络也得耗时半年;且要保障迁移期间内不再发生新故障,这是很难做到的。所以我们选择前期测试做得非常充分,来保障日后服务的可持续性。

InfoQ:又拍云专注于做图片的存储,你们提供了一些很有特色的服务(如缩略图、防盗链),同时非常专注于服务质量。相对于文件备份类的应用场景,海量小图片存储是非常吃资源的,你们在存储系统的设计上做了哪些工作以确保在资源占用高的情况下仍然能保持图片访问的服务质量?

黄慧攀:是的,UPYUN主要面向小于100MB的小文件提供服务,目前我们的存储集群已存有超过2PB的数据。面向海量小文件所面临的主要问题是:随机读取非常高、磁盘性能低;大家都知道缓存系统可以解决这类问题,而CDN其实就是个巨大的缓存系统,所以我们自建了CDN并对外提供服务。不仅能解决海量小文件所产生的磁盘性能问题,还能加速文件在互联网上的传输,一举两得。

InfoQ:UPYUN系统的测试是如何做的?

黄慧攀:我们团队还比较小,目前未专门设立测试部门,所有测试工作均由项目开发者来完成,毕竟开发人员更清楚会有哪些潜在问题,并制定自动化测试的样例。下面是我们一个项目的开发、测试与发布流程:

  1. 项目策划、文档和方案撰写
  2. 开发(过程中会有两名以上开发人员交叉 review)
  3. 本地测试(主要测试该项目的功能是否正常和程序稳定性、资源占用率等等)
  4. 模拟平台测试(主要测试该项目的功能上线是否对原平台上其他子系统产生不良影响,这里会有我们自己编写的一批批量测试脚本,以验证平台每项功能逻辑是否正确)
  5. 灰度测试(业务环境中抽取1%的服务器更新或指定某个别客户可使用该功能来进行测试)
  6. 全网发布

从整个流程来看,我们的测试周期是比较长的,测试工作占整个项目周期50%以上,甚至个别影响范围大的项目,测试周期会长达半年以上。

InfoQ:你们的团队是怎样分工的?研发跟产品运营、系统运维的同学又是如何沟通的?

黄慧攀:大家从我们的产品介绍上会知道我们主要提供3块服务,

  1. 云存储
  2. 云分发
  3. 云处理

所以我们的开发团队主要是根据这3个方向进行分组。现在我们团队分应用开发组和核心研发组,而在核心研发组中又分存储、分发、处理3个小组,分得比较细。因此我们的小组成员之间会有交叉分工,以便大家对整体系统能有充分的了解。

我们的产品服务与一般互联网服务不太一样,我们是以产品为主导而非运营主导,且我们的产品经理也是开发出身的,所以在与开发团队的协作沟通上不会存在什么问题。另外的运维部门则是更加紧密,因我们正在开始整个平台的自动化运维系统开发,我们的开发人员已走到一线,跟运维人员一起探讨运维自动化系统的功能性问题,开发人员能亲身了解运维工作和痛点,并以此来驱动运维自动化系统的开发工作。

InfoQ:这次QCon北京,你希望面向哪些人群进行分享?他们能从你的分享中获得什么?

黄慧攀:很感谢QCon能让我们来继续跟大家做些云计算方面的分享。在上一次的QCon上海大会我跟大家分享了又拍云的CDN技术,按我们公司的服务层次划分,这次的分享主题是我们在云存储系统的研发和构建过程中遇到的一些问题和经验。希望大家能通过我们这次的分享,对云存储能有更深入的了解,比如分布式算法、存储结构和日常维护等等。


[]解决eclipse导入erlang项目中文乱码

本文自动转发自我的博客: http://www.haofengjing.org/2516.html

eclipse可以很方便地管理erlang项目,eclipse对erlang的默认编码ISO-8859-1,所以就会经常发生中文乱码问题。文章将说明如何解决eclipse导入erlang项目中文乱码问题。

eclipse先导入erlang项目,再通过修改eclipse配置来解决乱码。如下:

Window Preferences > General Appearance > Content Types

在右边的栏目中,选Text,再选Erlang source file

ISO-8859-1改成UTF-8,然后点击右边的Update按钮即可。


[]基于 Erlang/OTP 搭建TCP服务器

本文自动转发自我的博客: http://www.haofengjing.org/2515.html

这两天在研究 erlang 如何构建 TCP 服务器,看到一篇文章,基于Erlang OTP构建一个TCP服务器,里面讲述了两种混合型Socket的实现方法,着实让人欢欣鼓舞。对比老外写的Building a Non-blocking TCP server using OTP principles,作者写的那个有点简单。本文将结合这两篇文章,继续讨论Erlang/OTP 构建TCP服务器的具体实现,以示例演示如何如何使用标准Erlang/OTP行为创建一个简单的无阻塞的TCP服务器。

TCP Socket模式

主动模式{active, true},非阻塞方式接收消息,但在系统无法应对超大流量请求时,客户端发送的数据过快,而且超过服务器可以处理的速度,那么,系统就可能会造成消息缓冲区被塞满,出现持续繁忙的流量的极端情况,系统因请求过多而溢出,造成Erlang虚拟机内存不足而崩溃。 被动模式{active, false},阻塞方式接收消息,底层的TCP缓冲区可用于抑制请求,并拒绝客户端的消息,在接收数据的地方都会调用gen_tcp:recv,造成阻塞(单进程模式下就只能消极等待某一个具体的客户端Socket ,很危险)。需要注意的是,操作系统可能还会做一些缓存允许客户端机器继续发送少量数据,然后才将其阻塞,但这个时候Erlang还没有调用recv函数。 混合型模式(半阻塞,{active, once}),主动套接字仅针对一条消息,在控制进程发送完一个消息数据后,必须显式地调用inet:setopts(Socket, [{active, once}]) 重新激活以便接受下一个消息(在此之前,系统处于阻塞状态)。可见,混合型模式综合了主动模式和被动模式的两者优势,可实现流量控制,防止服务器被过多消息淹没。 所以如果想构建TCP服务器,比较合理的是建立在TCP Socket 混合型模式(半阻塞)基础上。

TCP服务器设计

这个TCP服务器的设计包含了主应用程序 tcp_server_app 和监督者 tcp_server_sup 进程,监督者进程拥有 tcp_server_listener 和 tcp_client_sup 两个子进程。tcp_server_listener 负责处理客户端的连接请求,并通知 tcp_client_sup 启动一个 tcp_server_handler 实例进程来处理一条客户端的请求,然后由该实例进程负责处理服务器与客户端的交互数据。

应用程序和监督行为

为了构建一个 Erlang/OTP 应用程序,我们需要构建一些模块来实现应用程序和监督行为。当应用程序启动时,tcp_server_app:start/2 会调用 tcp_server_sup:start_link/1 来创建主监督进程。该监督进程通过回调 tcp_server_sup:init/1 来实例化子工作进程 tcp_server_listener 和子监督进程 tcp_client_sup。该子监督进程回调 tcp_server_sup:init/1 来实例化负责处理客户端连接的工作进程 tcp_server_handler。

TCP服务器应用程序 (tcp_server_app.erl)

[plain] view plaincopy
  1. -module(tcp_server_app).  
  2. -behaviour(application).  
  3. -export([start/2, stop/1]).  
  4. -define(PORT,  2222).  
  5.   
  6. start(_Type, _Args) ->  
  7.   io:format("tcp app start~n"),  
  8.   case tcp_server_sup:start_link(?PORT) of  
  9.     {ok, Pid} ->  
  10.       {ok, Pid};  
  11.     Other ->  
  12.       {error, Other}  
  13.   end.  
  14.   
  15. stop(_S) ->  
  16.   ok.  

TCP服务器监督者进程(tcp_server_sup.erl)

[plain] view plaincopy
  1. -module(tcp_server_sup).  
  2. -behaviour(supervisor).  
  3. -export([start_link/1, start_child/1]).  
  4. -export([init/1]).  
  5.   
  6. start_link(Port) ->  
  7.   io:format("tcp sup start link~n"),  
  8.   supervisor:start_link({local, ?MODULE}, ?MODULE, [Port]).  
  9.   
  10. start_child(LSock) ->  
  11.   io:format("tcp sup start child~n"),  
  12.   supervisor:start_child(tcp_client_sup, [LSock]).  
  13.   
  14. init([tcp_client_sup]) ->  
  15.   io:format("tcp sup init client~n"),  
  16.   {ok,  
  17.    { {simple_one_for_one, 0, 1},  
  18.     [  
  19.      { tcp_server_handler,   
  20.       {tcp_server_handler, start_link, []},  
  21.       temporary,   
  22.       brutal_kill,   
  23.       worker,   
  24.       [tcp_server_handler]  
  25.      }  
  26.     ]  
  27.    }  
  28.   };  
  29.   
  30. init([Port]) ->  
  31.   io:format("tcp sup init~n"),  
  32.   {ok,  
  33.     { {one_for_one, 5, 60},  
  34.      [  
  35.       % client supervisor  
  36.      { tcp_client_sup,   
  37.       {supervisor, start_link, [{local, tcp_client_sup}, ?MODULE, [tcp_client_sup]]},  
  38.       permanent,   
  39.       2000,   
  40.       supervisor,   
  41.       [tcp_server_listener]  
  42.      },  
  43.      % tcp listener  
  44.      { tcp_server_listener,   
  45.       {tcp_server_listener, start_link, [Port]},  
  46.       permanent,   
  47.       2000,   
  48.       worker,   
  49.       [tcp_server_listener]  
  50.      }  
  51.     ]  
  52.    }  
  53.   }.  
TCP服务器 Socket 监听进程(tcp_server_listener.erl)
[plain] view plaincopy
  1. -module(tcp_server_listener).  
  2. -behaviour(gen_server).  
  3. -export([start_link/1]).  
  4. -export([init/1, handle_call/3, handle_cast/2, handle_info/2,  
  5.          terminate/2, code_change/3]).  
  6. -record(state, {lsock}).  
  7.   
  8. start_link(Port) ->  
  9.   io:format("tcp server listener start ~n"),  
  10.   gen_server:start_link({local, ?MODULE}, ?MODULE, [Port], []).  
  11.   
  12. init([Port]) ->  
  13.   process_flag(trap_exit, true),  
  14.   Opts = [binary, {packet, 0}, {reuseaddr, true},  
  15.            {keepalive, true}, {backlog, 30}, {active, false}],  
  16.   State =  
  17.   case gen_tcp:listen(Port, Opts) of  
  18.     {ok, LSock} ->  
  19.       start_server_listener(LSock),  
  20.       #state{lsock = LSock};  
  21.     _Other ->  
  22.       throw({error, {could_not_listen_on_port, Port}}),  
  23.       #state{}  
  24.   end,  
  25.     {ok, State}.  
  26.   
  27. handle_call(_Request, _From, State) ->  
  28.   io:format("tcp server listener call ~p~n", [_Request]),  
  29.   {reply, ok, State}.  
  30.   
  31. handle_cast({tcp_accept, Pid}, State) ->  
  32.   io:format("tcp server listener cast ~p~n", [tcp_accept]),  
  33.   start_server_listener(State, Pid),  
  34.     {noreply, State};  
  35.   
  36. handle_cast(_Msg, State) ->  
  37.   io:format("tcp server listener cast ~p~n", [_Msg]),  
  38.   {noreply, State}.  
  39.   
  40. handle_info({'EXIT', Pid, _}, State) ->  
  41.   io:format("tcp server listener info exit ~p~n", [Pid]),  
  42.   start_server_listener(State, Pid),  
  43.   {noreply, State};  
  44.   
  45. handle_info(_Info, State) ->  
  46.   io:format("tcp server listener info ~p~n", [_Info]),  
  47.   {noreply, State}.  
  48.   
  49. terminate(_Reason, _State) ->  
  50.   io:format("tcp server listener terminate ~p~n", [_Reason]),  
  51.   ok.  
  52.   
  53. code_change(_OldVsn, State, _Extra) ->  
  54.   {ok, State}.  
  55.   
  56. start_server_listener(State, Pid) ->  
  57.   unlink(Pid),  
  58.   start_server_listener(State#state.lsock).  
  59.   
  60. start_server_listener(Lsock) ->  
  61.   case tcp_server_sup:start_child(Lsock) of  
  62.     {ok, Pid} ->  
  63.       link(Pid);  
  64.     _Other ->  
  65.       do_log  
  66.   end.  
TCP服务器处理客户端请求进程(tcp_server_handler.erl)
[plain] view plaincopy
  1. -module(tcp_server_handler).  
  2. -behaviour(gen_server).  
  3. -export([start_link/1]).  
  4. -export([init/1, handle_call/3, handle_cast/2, handle_info/2,  
  5.          terminate/2, code_change/3]).  
  6. -record(state, {lsock, socket, addr}).  
  7. -define(Timeout, 120*1000).  
  8.   
  9. start_link(LSock) ->  
  10.   io:format("tcp handler start link~n"),  
  11.   gen_server:start_link(?MODULE, [LSock], []).  
  12.   
  13. init([LSock]) ->  
  14.   io:format("tcp handler init ~n"),  
  15.   inet:setopts(LSock, [{active, once}]),  
  16.   gen_server:cast(self(), tcp_accept),  
  17.   {ok, #state{lsock = LSock}}.  
  18.   
  19. handle_call(Msg, _From, State) ->  
  20.   io:format("tcp handler call ~p~n", [Msg]),  
  21.   {reply, {ok, Msg}, State}.  
  22.   
  23. handle_cast(tcp_accept, #state{lsock = LSock} = State) ->  
  24.   {ok, CSock} = gen_tcp:accept(LSock),  
  25.   io:format("tcp handler info accept client ~p~n", [CSock]),  
  26.   {ok, {IP, _Port}} = inet:peername(CSock),  
  27.    start_server_listener(self()),  
  28.   {noreply, State#state{socket=CSock, addr=IP}, ?Timeout};  
  29.   
  30. handle_cast(stop, State) ->  
  31.   {stop, normal, State}.  
  32.   
  33. handle_info({tcp, Socket, Data}, State) ->  
  34.   inet:setopts(Socket, [{active, once}]),  
  35.   io:format("tcp handler info ~p got message ~p~n", [self(), Data]),  
  36.   ok = gen_tcp:send(Socket, <<Data/binary>>),  
  37.   {noreply, State, ?Timeout};  
  38.   
  39. handle_info({tcp_closed, _Socket}, #state{addr=Addr} = State) ->  
  40.   io:format("tcp handler info ~p client ~p disconnected~n", [self(), Addr]),  
  41.   {stop, normal, State};  
  42.   
  43. handle_info(timeout, State) ->  
  44.   io:format("tcp handler info ~p client connection timeout~n", [self()]),  
  45.   {stop, normal, State};  
  46.   
  47. handle_info(_Info, State) ->  
  48.   io:format("tcp handler info ingore ~p~n", [_Info]),  
  49.   {noreply, State}.  
  50.    
  51. terminate(_Reason, #state{socket=Socket}) ->  
  52.   io:format("tcp handler terminate ~p~n", [_Reason]),  
  53.   (catch gen_tcp:close(Socket)),  
  54.   ok.  
  55.   
  56. code_change(_OldVsn, State, _Extra) ->  
  57.   {ok, State}.  
  58.   
  59. start_server_listener(Pid) ->  
  60.   gen_server:cast(tcp_server_listener, {tcp_accept, Pid}).  
TCP服务器资源文件(tcp_server.app) 
[plain] view plaincopy
  1. {application,tcp_server,  
  2.   [{description,"TCP Server"},  
  3.    {vsn,"1.0.0"},  
  4.    {modules,[tcp_server,tcp_server_app,tcp_server_handler,  
  5.          tcp_server_listener,tcp_server_sup]},  
  6.    {registered,[]},  
  7.    {mod,{tcp_server_app,[]}},  
  8.    {env,[]},  
  9.    {applications,[kernel,stdlib]}]}.  

编译程序

为应用程序创建如下的目录结构:

[plain] view plaincopy
  1. ./tcp_server  
  2. ./tcp_server/ebin/  
  3. ./tcp_server/ebin/tcp_server.app  
  4. ./tcp_server/src/tcp_server_app.erl  
  5. ./tcp_server/src/tcp_server_sup.erl  
  6. ./tcp_server/src/tcp_server_listener.erl  
  7. ./tcp_server/src/tcp_server_handler.erl  

Linux:

[plain] view plaincopy
  1. $ cd tcp_server/src  
  2. $ for f in tcp*.erl ; do erlc -o ../ebin $f  
Windows:
[plain] view plaincopy
  1. cd tcp_server/src  
  2. for %%i in (tcp*.erl) do erlc -o ../ebin %%i  

运行程序

1、启动TCP服务器

[plain] view plaincopy
  1. erl -pa ebin  
  2. ...  
  3. 1> application:start(tcp_server).  
  4. tcp app start  
  5. tcp sup start link  
  6. tcp sup init  
  7. tcp sup init client  
  8. tcp server listener start  
  9. tcp sup start child  
  10. tcp handler start link  
  11. tcp handler init  
  12. ok  
  13. 2> appmon:start().  
  14. {ok,<0.41.0>}  

2、创建一个客户端来请求TCP服务器:
[plain] view plaincopy
  1. 3> f(S), {ok,S} = gen_tcp:connect({127,0,0,1},2222,[{packet,0}]).  
  2. {ok,#Port<0.1859>}  
  3. tcp handler info accept client #Port<0.1860>  
  4. tcp server listener cast tcp_accept  
  5. tcp sup start child  
  6. tcp handler start link  
  7. tcp handler init  

3、使用该请求向服务端发送消息:

[plain] view plaincopy
  1. 4> gen_tcp:send(S,<<"hello">>).  
  2. ok  
  3. tcp handler info <0.53.0> got message <<"hello">>  
4、接收到服务端发来的信息:

[plain] view plaincopy
  1. 5> f(M), receive M -> M end.  
  2. {tcp,#Port<0.1861>,"hello"}  
5、现在让我们尝试向服务端发送多个连接请求:

[plain] view plaincopy
  1. 6> gen_tcp:connect({127,0,0,1},2222,[{packet,0}]).  
  2. ...  
  3. 7> gen_tcp:connect({127,0,0,1},2222,[{packet,0}]).  
  4. ...  
  5. 8> gen_tcp:connect({127,0,0,1},2222,[{packet,0}]).  
  6. ...  


6、服务端程序写有超时功能,如果2分钟内没操作,连接将自动退出

[plain] view plaincopy
  1. 9> tcp handler info <0.39.0> client connection timeout  
  2. 9> tcp handler terminate normal  
  3. 9> tcp handler info <0.52.0> client connection timeout  
  4. 9> tcp handler terminate normal  
  5. 9> tcp handler info <0.54.0> client connection timeout  
  6. 9> tcp handler terminate normal  
  7. 9> tcp handler info <0.56.0> client connection timeout  
  8. 9> tcp handler terminate normal  

7、下面我们简单演示一下服务器的监督行为:

[plain] view plaincopy
  1. 9> exit(pid(0,58,0),kill).  
  2. tcp server listener info exit <0.58.0>  
  3. true  
  4. tcp sup start child  
  5. tcp handler start link  
  6. tcp handler init  

结束语

本例演示了如何创建一个简单的无阻塞的TCP服务器以及如何使用标准 Erlang/OTP 行为。作为一个练习,鼓励读者尝试通用的无阻塞TCP服务器功能抽象成一个独立式的行为。