之前实现的功能相当于一个大厅,这里我们为这个聊天室程序添加“主题房间的功能”。客户端登陆后处于大厅中,然后获取目前所有的主题房间,再发送消息进入到某个房间内。同一房间的成员可见,发送的消息也只限本房间可见。
为了管理这些房间,Erlang Opt中的监控树是最合适的。
新建一个模块room_manager.erl 其行为遵守gen_supervisor,子进程为chat_room
init(Para)->
{ok,
{
{one_for_one,3,5},
[
{the_room,
{chat_room,start_link,[]},
permanent,
10000,
worker
}
]
}
}
.
再添加一个方法用于启动这个supervisor
start_link(Para)->
supervisor:start_link({local,?MODULE},?MODULE, [Para])
.
如果现在就直接启动这个模块是没有问题的,但既然是主题房间,肯定不止一个吧。下面就为这个supervisor添加再启动主题房间的功能:
startNewRoom(Para)->
supervisor:start_child(the_room, [Para])
.
就这么简单? 肯定不是。还有以下几点需要考虑:
1.如何标识每个房间的信息,如名称、类型、编号、启动时间、当前用户数等。
2.如何维护每个房间的在线用户信息。
3.如何维护当前可用的主题房间的信息。
4.如何从大厅进入房间。
5.如何从房间退出到大厅。
6.如何将信息路由到自己所在的房间。
下面就一个一个地解决:
房间的标识:
如上代码启动主题房间的时候传入了Para参数,可以通过这个参数来初始化任何你需要的房间信息。PS: 这就是Erlang 的好处之一啊
修改chat_room.erl
start_link(Para)->
gen_server:start_link(?MODULE, [Para],[]).
定义一个record叫roominfo
-record(roominfo,{id,name,type,unum,creationDate}).
用传入进来的参数初始化房间信息
init([Para])->
id_generator:start_link(),
%ets:new(clientinfo,[public,
% ordered_set,
% named_table,
% {keypos,#clientinfo.id}
% ]),
client_manager:init(),
RoomId=id_generator:getnewid("room"),
case is_record(Para,roominfo)of
true->
{ok,#roominfo{unum=0}=Para};
Els->
%maybe we should throw exception here
{ok,#roominfo{id=RoomId,unum=0,name="room"++integer_to_list(RoomId),type="def"}}
end
.
每个房间的在线成员信息维护:
还记得client_manager这个模块否?这个模块就是用来专门管理房间对应的在线成员的,这里需要对其进行修改。
在前面的代码中client_manager这个模块的调用是与chat_room处于同一进程内的,也就是顺序性调用。
每次调用时所操纵的表是写死的,在这里需要改为动态的,在每次操纵时传入要操纵的表。
所以应该在chat_room中保存自己拥有的表名。
修改roominfo的定义添加tablename,并在初始化chat_room时生成好。
-record(roominfo,{id,name,type,unum,tablename,creationDate}).
init([Para])->
id_generator:start_link(),
%ets:new(clientinfo,[public,
% ordered_set,
% named_table,
% {keypos,#clientinfo.id}
% ]),
RoomId=id_generator:getnewid("room"),
ClientTableName=list_to_atom(?PRIFIX++integer_to_list(RoomId)),
client_manager:init(ClientTableName),
case is_record(Para,roominfo)of
true->
{ok,#roominfo{unum=0}=Para};
Els->
{ok,#roominfo{id=RoomId,unum=0,name="room"++integer_to_list(RoomId),type="def"}}
end
.
修改client_manger.erl为每个方法都添加table参数。
%% Author: Administrator
%% Created: 2012-2-28
%% Description: TODO: response for managing clientinfo
-module(client_manager).
%%
%% Include files
%%
-include("clientinfo.hrl").
-include("message.hrl").
%%
%% Exported Functions
%%
-export([init/1,addClient/2,updateClient/2,getNextClient/2,removeClient/2,getClient/2,getNick/2]).
%%
%% API Functions
%%
%%create clientinfo talbe here
init(Tab)->
ets:new(Tab,[public,
ordered_set,
named_table,
{keypos,#clientinfo.id}
])
.
%%
%% Local Functions
%%
%add new clientinfo
addClient(Tab,ClientInfo)->
ets:insert(Tab, ClientInfo)
.
%update clientinfo
updateClient(Tab,Message)->
#message{content=Info,from=Key}=Message,
io:format("content of message is:~p~n",[Info]),
%TODO:we should formart content from json to record #clientinfo
Rec =util_SetInfoParas:paraElements(Info),
io:format("parased record is:~p~n",[Rec]),
if is_record(Rec,clientinfo) ->
#clientinfo{nick=Nick,sex=Sex,age=Age,province=Province,
city=City,posx=Px,posy=Py}=Rec,
io:format("here Key is:~p~n",[Key]),
case ets:update_element(Tab, Key, [{#clientinfo.nick,Nick},
{#clientinfo.sex,Sex},
{#clientinfo.age,Age},
{#clientinfo.province,Province},
{#clientinfo.city,City},
{#clientinfo.posx,Px},
{#clientinfo.posy,Py}]) of
true->
{ok,Rec};
false->
{fals,Rec}
end;
true->
io:format("wrong format of clientinfo"),
{false,Rec}
end
.
%remove clientinfo
removeClient(Tab,Key)->
[TheKey]=Key,
case ets:lookup(Tab, TheKey) of
[Record]->
io:format("record tobe delete from clientinfo is:~p~n",[Record]);
[]->
io:format("no record found tobe delete from clientinfo~n")
end,
io:format("delete clientinfo from table key is:~p~n",[TheKey]),
ets:delete(Tab, TheKey)
.
getNick(Tab,Key)->
case ets:lookup(Tab, Key) of
[Record]->
#clientinfo{nick=Nick}=Record,
case Nick of
undefined ->
"client"++integer_to_list(Key);
Nick->
Nick
end;
[]->
"client"++integer_to_list(Key)
end
.
getClient(Tab,Key)->
ets:lookup(Tab, Key)
.
getNextClient(Tab,[Key])->
case ets:next(Tab, Key) of
'$end_of_table'->
io:format("getNext1,no key found~n"),
[];
Next->
io:format("getNext1,key found~p~n",[Key]),
ets:lookup(Tab, Next)
end
;
getNextClient(Tab,[])->
case ets:first(Tab) of
'$end_of_table'->
io:format("getNext,no key found~n"),
[];
Key->
io:format("getNext,key found~p~n",[Key]),
ets:lookup(Tab, Key)
end
.
再修改chat_room.erl中调用部分,为每个调用都添加table参数。
handle_call({remove_clientinfo,Ref},From,State)->
Key=Ref#clientinfo.id,
#roominfo{tablename=Table}=State,
client_manager:removeClient(Table,Key),
{reply,ok,State}
;
handle_call({sendmsg,Msg},From,State)->
%Key=ets:first(clientinfo),
%io:format("feching talbe key is ~p~n",[Key]),
%sendMsg(Key,Msg),
#roominfo{tablename=Table}=State,
sendMsg(Table,Msg,[]),
{reply,ok,State}
;
handle_call({get_member,Msg},From,State)->
#message{content=Content}=Msg,
#roominfo{tablename=Table}=State,
case binary_to_list(Content) of
"all"->
List=getClient(Table,[],[]),
#message{from=Id}=Msg,
TheFrom=client_manager:getNick(Table,Id),
Message=Msg#message{type="result",from=TheFrom,subject="clientinfo",content=List},
{Pid,_}=From,
Pid!{get_member,Message};
_Els->
ok
end,
{reply,ok,State}
;
handle_call({add_clientinfo,ClientInfo},From,State)->
%store clientinfo to ets
%ets:insert(clientinfo, NewRec)
#roominfo{tablename=Table}=State,
client_manager:addClient(Table,ClientInfo),
{reply,ok,State}
;
handle_call({set_clientinfo,Message},From,State)->
%we can send result message to client
%or send a broadcast message to all client
#message{from=Id}=Message,
#roominfo{tablename=Table}=State,
[Client]=client_manager:getClient(Table,Id),
#clientinfo{pid=Pid}=Client,
TheNick=client_manager:getNick(Table,Id),
case client_manager:updateClient(Table,Message)of
{ok,Rec}->
#clientinfo{nick=Nick}=Rec,
Pid!{setinfo,Rec},
{Content}={TheNick ++ " has change nickname:" ++ Nick},
NewMessage=#message{id="0",
from=Id,
to="",
content=Content,
type="msg",
subject="chat",
time=Message#message.time},
io:format("Notice Message is:~p~n",[NewMessage]),
sendMsg(Table,NewMessage,[]);
{false,Rec}->
ok
end,
{reply,ok,State}
.
sendMsg(Table,Msg,Key)->
io:format("broading casting msg~p~n",[Key]),
case client_manager:getNextClient(Table,Key)of
[Record]->
Next=Record#clientinfo.id,
Pid=Record#clientinfo.pid,
Pid!{dwmsg,Msg},
sendMsg(Table,Msg,[Next]);
[]->
ok
end
.
getClient(Table,Key,UList)->
case client_manager:getNextClient(Key) of
[Record]->
Next=Record#clientinfo.id,
io:format("record is:~p~n",[Record]),
Json=util_SetInfoParas:deparaElement(Record),
io:format("clientSession found:~p~n",[Record]),
NewUList=[Json|UList],
getClient(Table,[Next],NewUList);
[]->
UList
end
.
如此即可。注:client_manager有一个getNick/1 函数,负责返回对应ID用户的昵称。如何从正确的表格中找出用户的昵称?这个问题将留在用户消息路由部分去处理。
暂时到这。