- 浏览: 194253 次
最新评论
-
lord_is_layuping:
...
弄了弄scala -
yuqilin001:
// Close the connection if the ...
人生就是不断的转圈子——一个最简单的jboss netty chat测试程序 -
jila:
我当然知道,这是海子的诗,而且,那个女的是用地方话念的,别有味 ...
面朝大海,春暖花开 -
fitliving:
请问您如何让 netty 不用 ChannelBuffer这数 ...
人生就是不断的转圈子——一个最简单的jboss netty chat测试程序 -
mryufeng:
Erlang语言成分在整个otp平台20%的分量都不到 所以他 ...
erlang的优秀之处
在《programming erlang》中,lib_chan是比较复杂的代码,至少我是读了好多遍才算明白。
我不知道是这段代码太复杂,还是我的智商有问题。至少,我不太习惯erlang的这种方式。不过,我很喜欢这个lib_chan的思路和erlang程序的风格。这种方式非常的明确,直接。我不知道为什么erlang没有一个类似的成熟的库。这个lib_chan好像是joe armstrong为这本书写的,难道erlang根本不需要这样的东西?
最好结合书里的那个聊天程序来理解lib_chan。这确实是个很精巧的东西(lib_chan还谈不上精巧,那个chat程序才精巧)。
注意:我只解释重要的地方。
lib_chan是一个类似总外壳的东西,定义接口,连接起诸多细节,这里我去掉了一些琐碎的函数。
下面的这个是lib_chan_cs,它构造了服务器端的结构和机制。
而lib_chan_mm则很简单,只是翻译了一下数据。
所以,关于lib_chan_mm我也不想解释什么,如果你读不懂,那么肯定是你自己的问题了。
最后,我在说些我遇到的问题。
现在我的问题是某些进程在系统关闭后,没有被关闭掉。
也就是说一旦运行lib_chan后,关闭的时候,清理不干净。
比如,portServer就没关闭掉,还有两三个进程没被关闭掉。
顺便再提一下otp。
我曾经很迷惑对otp的使用。最近又看了一遍后,我好像明白一点了。
我注意到otp中并没有socket,accept,gen_tcp,之类的通信程序。
也就是说,otp是一个应用程序框架,不是分布式,也不是网络。
不过,它是并发的,有很多的进程在合作着,而且,erlang进程收消息,本身就是支持并发(不管多少个进程同时给它发消息,都会排队进自己的邮箱)。
所以,一旦我想用otp写一个网络程序,可行的方法就是把otp程序作为一个稳定的后端,分布式和通信用单独的程序来做,然后调用用otp写的模块。正如书中那个web后端的例子。
不错的分析,能否转载?
当然,这本来就没什么大不了的。
看了我的这个,有帮助就好。
我不知道是这段代码太复杂,还是我的智商有问题。至少,我不太习惯erlang的这种方式。不过,我很喜欢这个lib_chan的思路和erlang程序的风格。这种方式非常的明确,直接。我不知道为什么erlang没有一个类似的成熟的库。这个lib_chan好像是joe armstrong为这本书写的,难道erlang根本不需要这样的东西?
最好结合书里的那个聊天程序来理解lib_chan。这确实是个很精巧的东西(lib_chan还谈不上精巧,那个chat程序才精巧)。
注意:我只解释重要的地方。
lib_chan是一个类似总外壳的东西,定义接口,连接起诸多细节,这里我去掉了一些琐碎的函数。
%% --- %% Excerpted from "Programming Erlang", %% published by The Pragmatic Bookshelf. %% Copyrights apply to this code. It may not be used to create training material, %% courses, books, articles, and the like. Contact us if you are in doubt. %% We make no guarantees that this code is fit for any purpose. %% Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information. %%--- -module(lib_chan). -export([cast/2, start_server/0, start_server/1, connect/5, disconnect/1, rpc/2]). -import(lists, [map/2, member/2, foreach/2]). -import(lib_chan_mm, [send/2, close/1]). %%---------------------------------------------------------------------- %% Server code start_server1(ConfigData) -> register(lib_chan, spawn(fun() -> start_server2(ConfigData) end)). start_server2(ConfigData) -> [Port] = [ P || {port,P} <- ConfigData], start_port_server(Port, ConfigData). start_port_server(Port, ConfigData) -> %% lib_chan_cs:start_raw_server 函数会启动端口监听,并且,一旦接受连接, %% 也就是说,拿到一个Socket的时候(一个Channel),就给现在传进来的fun(Socket)。 %% %% 也就是说,得到Socket就是lib_chan_cs:start_raw_server的第一阶段的成果, %% 之后,它的任务就是把拿到的Socket给fun(Socket),就这么简单。 %% %% 而fun(Socket)做的事情就是,start_port_instance(Socket,ConfigData)。 %% 看见了吗?我们现在有了Socket(先不必考虑监听,accept之类的事),并且, %% 我们可以从ConfigData里拿到要运行的目标(一个函数,或者说是一个入口)。 %% 现在,你需要的都全了吧?!更细节的是下一步了。 lib_chan_cs:start_raw_server(Port, fun(Socket) -> start_port_instance(Socket, ConfigData) end, 100, 4). start_port_instance(Socket, ConfigData) -> %% 现在,我们已经有了Socket,我们还知道ConfigData,但是,Socket不是MM, %% 我们必须弄成中间人模式,所以,要用lib_chan_mm:loop(Socket,Controller)。 %% 谁运行lib_chan_mm:loop/2,谁就是MM。明白了吧? %% 那么,在运行lib_chan_mm:loop/2之前,必须的搞一个Controller,也就是说, %% MM必须的知道把自己收到的消息给谁,这个谁就是Controller。 %% Controller也是个process,所以要spawn。 S = self(), Controller = spawn_link(fun() -> start_erl_port_server(S, ConfigData) end), lib_chan_mm:loop(Socket, Controller). start_erl_port_server(MM, ConfigData) -> %% 这个函数是Controller要运行的,我知道MM会给我消息,我也知道我要运行的东西 %% 在ConfigData里。 receive {chan, MM, {startService, Mod, ArgC}} -> case get_service_definition(Mod, ConfigData) of {yes, Pwd, MFA} -> case Pwd of none -> send(MM, ack), really_start(MM, ArgC, MFA); _ -> do_authentication(Pwd, MM, ArgC, MFA) end; no -> io:format("sending bad service~n"), send(MM, badService), close(MM) end; Any -> io:format("*** ErL port server got:~p ~p~n",[MM, Any]), exit({protocolViolation, Any}) end. %% MM is the middle man %% Mod is the Module we want to execute ArgC and ArgS come from the client and %% server respectively really_start(MM, ArgC, {Mod, Func, ArgS}) -> %% 这就是start_erl_port_server/2的推进版,已经把ConfigData解析成mfa。 %% Controller最终与运行到这里,也就是说Controller会运行apply(Mod,Func,[MM,ArgC,ArgS])。 %% ok! case (catch apply(Mod,Func,[MM,ArgC,ArgS])) of {'EXIT', normal} -> true; {'EXIT', Why} -> io:format("server error:~p~n",[Why]); Why -> io:format("server error should die with exit(normal) was:~p~n", [Why]) end. %% get_service_definition(Name, ConfigData) get_service_definition(Mod, [{service, Mod, password, Pwd, mfa, M, F, A}|_]) -> {yes, Pwd, {M, F, A}}; get_service_definition(Name, [_|T]) -> get_service_definition(Name, T); get_service_definition(_, []) -> no.
下面的这个是lib_chan_cs,它构造了服务器端的结构和机制。
而lib_chan_mm则很简单,只是翻译了一下数据。
所以,关于lib_chan_mm我也不想解释什么,如果你读不懂,那么肯定是你自己的问题了。
%% --- %% Excerpted from "Programming Erlang", %% published by The Pragmatic Bookshelf. %% Copyrights apply to this code. It may not be used to create training material, %% courses, books, articles, and the like. Contact us if you are in doubt. %% We make no guarantees that this code is fit for any purpose. %% Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information. %%--- -module(lib_chan_cs). %% cs stands for client_server -export([start_raw_server/4, start_raw_client/3]). -export([stop/1]). -export([children/1]). %% start_raw_server(Port, Fun, Max) %% This server accepts up to Max connections on Port %% The *first* time a connection is made to Port %% Then Fun(Socket) is called. %% Thereafter messages to the socket result in messages to the handler. %% tcp_is typically used as follows: %% To setup a listener %% start_agent(Port) -> %% process_flag(trap_exit, true), %% lib_chan_server:start_raw_server(Port, %% fun(Socket) -> input_handler(Socket) end, %% 15, %% 0). start_raw_client(Host, Port, PacketLength) -> gen_tcp:connect(Host, Port, [binary, {active, true}, {packet, PacketLength}]). %% Note when start_raw_server returns it should be ready to %% Immediately accept connections start_raw_server(Port, Fun, Max, PacketLength) -> %% 从这里启动服务器。 %% 注意,它并没有自己直接去启动,而是用一个进程去干这事。 %% %% 这个进程叫portServer8080,运行cold_start/5。 %% 注意,8080是我编的,如果端口是3333,那就叫portServer3333,这是port_name搞的。 %% %% 为啥叫cold_start呢?因为,cold_start只是用gen_server:listen注册了要监听端口 %% 和某些配置信息,并没有调用gen_server:accept来接受连接。 Name = port_name(Port), case whereis(Name) of undefined -> Self = self(), Pid = spawn_link(fun() -> cold_start(Self,Port,Fun,Max,PacketLength) end), receive {Pid, ok} -> register(Name, Pid), {ok, self()}; {Pid, Error} -> Error end; _Pid -> {error, already_started} end. stop(Port) when integer(Port) -> Name = port_name(Port), case whereis(Name) of undefined -> not_started; Pid -> exit(Pid, kill), (catch unregister(Name)), stopped end. children(Port) when integer(Port) -> port_name(Port) ! {children, self()}, receive {session_server, Reply} -> Reply end. port_name(Port) when integer(Port) -> list_to_atom("portServer" ++ integer_to_list(Port)). cold_start(Master, Port, Fun, Max, PacketLength) -> %% 现在,运行本函数的就是那个叫portServer8080的那个进程了。 %% 没错,它确实是一切事情的起点。但是,它除了注册监听信息, %% 所做的事情也只有两个,一个是start_accept/2,一个是启动socket_loop/5。 %% %% start_accept会创建一个进程,开始真正的接受连接,然后发消息给portServer8080, %% portServer8080它正在运行socket_loop。 %% %% 这里必须的说明一下,start_accept一旦接受一个连接后,就没人在接受连接了, %% (这是一段真空时间)所以,要通知portServer,自己已经在处理一个连接, %% portServer会决定是不是再新建一个start_accept再接受连接。portServer就是这样 %% 管理连接个数的。 process_flag(trap_exit, true), %% io:format("Starting a port server on ~p...~n",[Port]), case gen_tcp:listen(Port, [binary, %% {dontroute, true}, {nodelay,true}, {packet, PacketLength}, {reuseaddr, true}, {active, true}]) of {ok, Listen} -> %% io:format("Listening to:~p~n",[Listen]), Master ! {self(), ok}, New = start_accept(Listen, Fun), %% Now we're ready to run socket_loop(Listen, New, [], Fun, Max); Error -> Master ! {self(), Error} end. socket_loop(Listen, New, Active, Fun, Max) -> %% 一旦start_accept进程(也就是运行start_child的进程),接受到连接, %% 会发一个消息{istarted,MyPid},给portServer,portServer运行的是Socket_loop, %% 也就是到现在这个地方。 %% %% possibly_start_another的任务是看看现在连接的个数是否达到最大,如果到了极限, %% 就不运行start_accept,直接运行socket_loop(Listen, false, Active, Fun, Max)。 %% 一旦有某些连接没了,会收到{'EXIT', Pid, _Why}这样的消息,再看看连接个数, %% 以决定是否start_accept去监听。 receive {istarted, New} -> Active1 = [New|Active], possibly_start_another(false,Listen,Active1,Fun,Max); {'EXIT', New, _Why} -> %% io:format("Child exit=~p~n",[Why]), possibly_start_another(false,Listen,Active,Fun,Max); {'EXIT', Pid, _Why} -> %% io:format("Child exit=~p~n",[Why]), Active1 = lists:delete(Pid, Active), possibly_start_another(New,Listen,Active1,Fun,Max); {children, From} -> From ! {session_server, Active}, socket_loop(Listen,New,Active,Fun,Max); _Other -> socket_loop(Listen,New,Active,Fun,Max) end. possibly_start_another(New, Listen, Active, Fun, Max) %% 这个函数和socket_loop纠缠在一起,看看socket_loop的注释吧! when pid(New) -> socket_loop(Listen, New, Active, Fun, Max); possibly_start_another(false, Listen, Active, Fun, Max) -> case length(Active) of N when N < Max -> New = start_accept(Listen, Fun), socket_loop(Listen, New, Active, Fun,Max); _ -> socket_loop(Listen, false, Active, Fun, Max) end. start_accept(Listen, Fun) -> S = self(), spawn_link(fun() -> start_child(S, Listen, Fun) end). start_child(Parent, Listen, Fun) -> %% 注意这个child是个进程,因为一旦它拿到连接,本身还要变成MM呢! %% 在Fun(Socket)的时候,还记得那个start_port_instance吗? %% 还记得start_port_instance中的lib_chan_mm:loop(Socket,Controller)吗? %% %% 我再说一遍,在接到Socket后,会运行 %% fun(Socket) ->start_port_instance(Socket,ConfigData) end %% 这个东西。 case gen_tcp:accept(Listen) of {ok, Socket} -> Parent ! {istarted,self()}, % tell the controller inet:setopts(Socket, [{packet,4}, binary, {nodelay,true}, {active, true}]), %% before we activate socket %% io:format("running the child:~p Fun=~p~n", [Socket, Fun]), process_flag(trap_exit, true), case (catch Fun(Socket)) of {'EXIT', normal} -> true; {'EXIT', Why} -> io:format("Port process dies with exit:~p~n",[Why]), true; _ -> %% not an exit so everything's ok true end end.
最后,我在说些我遇到的问题。
现在我的问题是某些进程在系统关闭后,没有被关闭掉。
也就是说一旦运行lib_chan后,关闭的时候,清理不干净。
比如,portServer就没关闭掉,还有两三个进程没被关闭掉。
顺便再提一下otp。
我曾经很迷惑对otp的使用。最近又看了一遍后,我好像明白一点了。
我注意到otp中并没有socket,accept,gen_tcp,之类的通信程序。
也就是说,otp是一个应用程序框架,不是分布式,也不是网络。
不过,它是并发的,有很多的进程在合作着,而且,erlang进程收消息,本身就是支持并发(不管多少个进程同时给它发消息,都会排队进自己的邮箱)。
所以,一旦我想用otp写一个网络程序,可行的方法就是把otp程序作为一个稳定的后端,分布式和通信用单独的程序来做,然后调用用otp写的模块。正如书中那个web后端的例子。
评论
4 楼
美洲豹
2009-07-28
otp帮你实现了错误处理,包括进程结束要执行的东西(terminate回调),
而且非常精确,除非是系统kill掉进程,一般的进程崩溃,都会正确的调用terminate
对维护在线列表非常方便
lib_chan 更适合客户端和服务端都使用erlang的系统,或者erlang系统之间的相互调用
但相对于分布式调用过于复杂
我偶得一个基于otp框架的 socket server 模板
只要增加一点点逻辑,就能实现聊天室服务端
而且非常精确,除非是系统kill掉进程,一般的进程崩溃,都会正确的调用terminate
对维护在线列表非常方便
lib_chan 更适合客户端和服务端都使用erlang的系统,或者erlang系统之间的相互调用
但相对于分布式调用过于复杂
我偶得一个基于otp框架的 socket server 模板
只要增加一点点逻辑,就能实现聊天室服务端
3 楼
mryufeng
2009-07-09
lib_chan只是提了个思路 工程实践上并不是最高效的 bob写的mochiweb才是真正注重效率
2 楼
wenjixiao
2009-07-06
jackyz 写道
不错的分析,能否转载?
当然,这本来就没什么大不了的。
看了我的这个,有帮助就好。
1 楼
jackyz
2009-07-06
不错的分析,能否转载?
lib_chan/otp 这些东西如果从 EJB/Spring 这样的角度去类比,理解起来就会比较容易抓住要点。
它们是“支架”(框架),通过 MFA/Behavior 以及其他的语言设施(Interface),你能够将你的业务代码(Bean)以比较简单的方式“挂接”到这样的支架之中。因为支架本身已经提供了完善的“非功能性”能力(比如分布式),所以,你的业务代码能迅速的作为一个部分(EJB),融入整个系统。
lib_chan/otp 这些东西如果从 EJB/Spring 这样的角度去类比,理解起来就会比较容易抓住要点。
它们是“支架”(框架),通过 MFA/Behavior 以及其他的语言设施(Interface),你能够将你的业务代码(Bean)以比较简单的方式“挂接”到这样的支架之中。因为支架本身已经提供了完善的“非功能性”能力(比如分布式),所以,你的业务代码能迅速的作为一个部分(EJB),融入整个系统。
发表评论
-
erlang's list
2010-12-20 14:30 2695在看《programming erlang》 ... -
erlang的优秀之处
2010-03-24 14:49 1675erlang是我见过的最简洁和实用以及符合直觉的语言。 现在, ... -
my linux
2009-12-30 15:19 717最近,信心大涨。 linux的内存管理,我终于理的差不多了。 ... -
5k之后
2009-10-19 11:44 754这个周末,我打上了5k。 事实上这好像并不是什么难事,下着下着 ... -
lib_chan 简化版
2009-10-15 15:27 1583我觉得那个lib_chan太烦,用法我也觉得不那么清晰,反正我 ... -
昨天五局的总结
2009-09-17 09:27 797昨天,我下了五局。三胜两败。 败的都是好局。 我发现,我现在的 ... -
java scala vs erlang c
2009-09-01 16:32 2529说来惭愧,自己一向不成才,语言研究过很多,可是,到现在也没有一 ... -
弄了弄scala
2009-08-27 15:46 2093如果把裸体看作计算机 ... -
新的疑惑
2009-08-21 09:36 862我做Orange的时候发现我 ... -
开始懂了
2009-08-03 15:38 631在某个不经意的时候。 我把,linux,windows,com ... -
ruby's block and closure
2009-05-31 11:35 997端午放假的这几天,由于无聊,我又看了看ruby。 但是还是没有 ... -
不过尔尔
2009-03-31 15:03 732公式解释器(电子表格)实现的差不多了。 原来也不过如此而已。 ... -
关于程序语言
2009-03-19 10:39 768刚才,看一些d语言的资料,看到一个词“语言纯粹主义者”。 这很 ... -
慢慢的知道
2009-03-16 10:37 853今天我醒的太早,于是 ... -
最近有点迷惑
2009-03-12 14:48 800最近,有点迷惑,也有 ... -
遇到障碍
2009-02-17 11:28 875最近,遇到点麻烦,应该说那是相当的麻烦! 这种解析器实在是不容 ... -
有点不会了
2009-02-10 10:39 666我发现,我现在做的东西是我到现在为止做的最难的东西。 我确实有 ... -
plt scheme实现的围棋程序初步
2008-12-01 16:15 1691我用scheme实现了一个weiqi程序的基本框架,事实上我的 ... -
睡觉过多
2008-11-12 15:38 869这两天,睡觉过多—— ... -
scons visual studio 9 vista
2008-10-28 13:17 2000今天,我想把我的scons迁移到vista机上。发现,scon ...
相关推荐
"lib_mysqludf_sys 的win版本dll库"这一标题明确指出,我们正在讨论的是一个专为Windows操作系统设计的动态链接库(Dynamic Link Library, DLL)文件,名为lib_mysqludf_sys。DLL是Windows系统中用于封装可重用代码...
《MySQL系统命令UDF——lib_mysqludf_sys详解》 MySQL是一种广泛使用的开源关系型数据库管理系统,其功能强大,灵活性高。在实际应用中,有时我们需要执行一些操作系统级别的任务,如读取或写入文件、执行系统命令...
stm32f10x标准固件库的帮助文档(stm32f10x_stdperiph_lib_um.chm) stm32f10x标准固件库的帮助文档(stm32f10x_stdperiph_lib_um.chm) stm32f10x标准固件库的帮助文档(stm32f10x_stdperiph_lib_um.chm) stm32f10...
STM32多种外设的开发例程:路径STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\ADC STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\BKP STM32F10x_StdPeriph_Lib_V...
标题 "murata_lib_ads_s_2106.zip" 提供的信息表明,这是一个与村田(Murata)元器件相关的库文件,适用于ADS2016。ADS是Analog Device Studio的简称,是一款广泛用于射频(RF)、微波以及毫米波电路设计的仿真软件...
《博途TIA V17运动库文件:Drive_Lib_S7_1200_1500_TIA V17详解》 在工业自动化领域,西门子的TIA博途(Totally Integrated Automation Portal)是一款广泛使用的集成工程软件,它涵盖了从设计、编程到调试的全生命...
为解决这一问题,我们有了一款名为“79047707_LSim_LIB_V3_0_0”的压缩包文件,它提供了一个专门用于仿真PID控制中PV值反馈的库。 首先,我们要理解什么是LSim LIB。LSim LIB是一个仿真库,专为PLC编程和仿真环境...
STM32F10x_StdPeriph_Lib_V3.5.0是一个针对STM32F10x系列微控制器的官方标准外设库,它由意法半导体(STMicroelectronics)开发,专为基于ARM Cortex-M3内核的芯片设计。这个库包含了丰富的驱动程序,使得开发者可以...
ADC examples 3 & 4 updated DEBUG example Example modified to support RIDE specific printf function implementation I2C example5 i2c_ee.c: add the following function prototypes: "...
stm32f4xx_dsp_stdperiph_lib_um.chm
STM32F10x_StdPeriph_Lib_V3.6.0.zip 是一个与STM32F10x系列微控制器相关的软件开发资源包,主要用于帮助开发者在STM32F103C8T6这款芯片上进行嵌入式程序开发。STM32F10x系列是意法半导体(STMicroelectronics)推出...
STM32F10x_StdPeriph_Lib_V3.5.0是一个针对STM32F103系列微控制器的官方标准外设库,它为开发者提供了丰富的API(应用程序编程接口),使得开发者能够轻松地访问和控制STM32F103芯片上的各种外设。这个库包含了驱动...
流量累积功能块FB_函数库_Totalizer_Lib_TIA_Portal_V15版本是一个专为自动化工程设计的软件资源,适用于Siemens TIA Portal V15集成自动化环境中。该资源包含了一个专门用于处理流量累积的功能块(FB),是工业自动...
STM32F10x_StdPeriph_Lib_V3.5.0 是一款针对STM32F10X系列微控制器的固件库,由意法半导体(STMicroelectronics)官方发布,版本号为3.5.0。该库是STM32开发的重要组成部分,它提供了丰富的标准外设驱动程序,简化了...
编译NRF51822的dfu例程遇到micro_ecc_lib_nrf51.lib库缺失。放入nRF5_SDK_12.3.0_d7731ad\external\micro-ecc\nrf51_keil\armgcc
VC代码 cj60lib_src (实用代码源)VC代码 cj60lib_src (实用代码源)VC代码 cj60lib_src (实用代码源)VC代码 cj60lib_src (实用代码源)VC代码 cj60lib_src (实用代码源)VC代码 cj60lib_src (实用代码源)VC代码 cj60lib...
汉化版的用户手册(STM32F10x_stdperiph_lib_um_汉化.chm)是为了方便中国开发者阅读和理解而制作的,它将原始的英文文档翻译成了中文,消除了语言障碍,提高了开发效率。 STM32F10X系列是基于ARM Cortex-M3内核的...
lib_wwapi-2.0.12.6.aar
STM32F4xx_DSP_StdPeriph_Lib_V1.4.0.zip 是一个针对STM32F4系列微控制器的数字信号处理(DSP)和标准外设库的压缩包。这个库是STMicroelectronics公司为开发者提供的,旨在简化STM32F4设备的软件开发过程,提供了一...