之前实现的功能相当于一个大厅,这里我们为这个聊天室程序添加“主题房间的功能”。客户端登陆后处于大厅中,然后获取目前所有的主题房间,再发送消息进入到某个房间内。同一房间的成员可见,发送的消息也只限本房间可见。
为了管理这些房间,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用户的昵称。如何从正确的表格中找出用户的昵称?这个问题将留在用户消息路由部分去处理。
暂时到这。