之前写过“从5900到65535之间随机分配一个数字——erlang实现”,
具体的地址是:http://blog.chinaunix.net/space.php?uid=22566367&do=blog&id=2974384
不过当时用到了数据库,如果使用数据库的话,这么一来,分配的效率就大打折扣。为此,我进行了改进,使用的gen_fsm模型,结合linux内核的pid位图算法,进行了设计。
有关gen_fsm模型,简单的说就是:
State(S) + Event(E) -> Actions(A),State(S')
表示的就是在S状态时如果有事件E发生,那么执行动作A后把状态调整到S’。
背景介绍:
vnc是一款开源的远程控制软件,它能完整的将窗口界面,通过网络传输到另外一台计算机的屏幕上。在linux下,vnc包括四个命令:vncserver,vncviewer,vncpasswd,vncconnect。在连接一台计算机的时候,需要知道对方计算机的IP和端口号,在我们的项目中,虚拟机的端口号是由master分配的,因此,master就要实现vncport的分配了,vncport的分配范围一般在5900到65535之间。
我们的目标是使用elang实现这个vncport的分配,具体要求如下:
1、每次从5900~65535之间分配一个数字,而且这每次分配的数字不能重复。
2、可以对使用完毕的数字进行回收。
3、如果没有可供分配的数字,那么退出。
咋一看,觉得这个不是很难,但是,如果是这这么大的数据量之间的话,这个不是很易的。
这个分配的思想和进程的pid分配有些相似(可以说是一样的),因此,我们可以借用pid的位图算法进行erlang的算法设计。
这里的最大问题在于,在erlang中,没有我们在c语言中方便使用的那些变量,而在每次分配一个vncport之后,我们是需要记录这个位图的呀,这可怎么办呀?
于是我想到了gen_fsm模型。这里对pid位图不作详细解释了,具体可以参考我的博客:
http://blog.chinaunix.net/space.php?uid=22566367&do=blog&id=2845765
下面开始我们的gen_fsm进行相应的vncport分配。
分析:使用一个数据记录0~65535之间每一个数字的分配情况,如果分配的话就置为1,没有分配的话就是0。这样的话把这个记录转化为二进制的话会是65536个bit,这个数字是很大的,占用8KB的空间,所以如果在涉及到erlang函数传递的话,使用尾部递归比较好。
好了,大体的思想其实就这么简单,关键就是看如何实现了。
我们可以把0~65535看作一个数字仓库,这个仓库是存放这65536个数字的。这个仓库只有一个状态——使用(没有设置结束状态,原因是如果到结束状态的话,直接退出这个程序)。
接下来,就是按照gen_fsm的模型要求,我们进行相应的回调模块的编写。
%% vncport.erl
-module(vncport).
-behaviour(gen_fsm).
-author(sunny).
-export([start/0, get_one/0, free_one/1]).
%% gen_fsm callbacks
-export([init/1,
alloc_ing/2,
handle_event/3,
handle_sync_event/4,
handle_info/3,
terminate/3,
code_change/4]).
%%
%% API functions
%%
start() ->
gen_fsm:start_link({local, ?MODULE}, ?MODULE, [], []).
get_one() ->
gen_fsm:send_event(?MODULE, {alloc}).
free_one(N) ->
gen_fsm:send_event(?MODULE, {free, N}).
%%
%% gen_fsm callbacks
%%
init(_Data) ->
Last = -1,
VncMap = set_bit(set, {0, 5900, 0}),
{ok, alloc_ing, {Last, VncMap}}.
alloc_ing({alloc}, {Last, VncMap}) ->
io:format("alloc_ing + alloc -> alloc_ing + Action1.~n"),
P = alloc_vnc(Last, VncMap, 0),
case P of
{Next, NewMap} ->
{next_state, alloc_ing, {Next, NewMap}};
_ ->
io:format("stop gen_fsm~n"),
{stop, normal, {alloc_over}}
end;
alloc_ing({free, N}, {Last, VncMap}) ->
io:format("alloc_ing + free -> alloc_ing + Action2.~n"),
NewMap = (bnot (1 bsl N)) band VncMap,
{next_state, alloc_ing, {Last, NewMap}}.
handle_event(_A, _B, _C) ->
{next_state, ok, ok}.
handle_sync_event(_A, _B, _C, _D) ->
{reply, ok, ok, ok}.
handle_info(_A, _B, _C) ->
{next_state, ok, ok}.
code_change(_A, _B, _C, _D) ->
{ok, ok, ok}.
terminate(Reason, LastState, StateData) ->
io:format("alloc over,Reason:~p, Last State:~p, State Data:~p~n", [Reason, LastState, StateData]),
ok.
%%
%% Local functions
%%
%% alloc_vnc(Last, VncMap, 0) | alloc_vnc(Last, VncMap, 1)
%% 0:the vncmap is not allocated over
%% 1:the vncmap is allocated over
alloc_vnc(Last, VncMap, 0) ->
if
Last+1 =:= 65536 ->
Flag = 1,
Current = 0;
true ->
Flag = 0,
Current = Last+1
end,
Temp = (1 bsl Current) band VncMap,
case Temp of
0 ->
io:format("Alloc result is:~p~n", [Current]),
NewMap = (1 bsl Current) bor VncMap,
{Current, NewMap};
_ ->
alloc_vnc(Current, VncMap, Flag)
end;
alloc_vnc(_Last, _VncMap, 1) ->
{vnc_alloc_over}.
%% set_bit(not_set, {InitMap}) | set_bit(set, {From, To, InitMap})
set_bit(not_set, {VncMap}) ->
VncMap;
set_bit(set, {From, From, VncMap}) ->
NewMap = (1 bsl From) bor VncMap,
NewMap;
set_bit(set, {From, To, VncMap}) ->
NewMap = (1 bsl From) bor VncMap,
set_bit(set, {From+1, To, NewMap}).
一个测试结果:
1> c(vncport). {ok,vncport} 2> vncport:start(). {ok,<0.42.0>} 3> vncport:get_one(). alloc_ing + alloc -> alloc_ing + Action1. ok Alloc result is:5901 4> vncport:get_one(). alloc_ing + alloc -> alloc_ing + Action1. ok Alloc result is:5902 5> vncport:get_one(). alloc_ing + alloc -> alloc_ing + Action1. ok Alloc result is:5903 6> vncport:get_one(). alloc_ing + alloc -> alloc_ing + Action1. ok Alloc result is:5904 7> |