接下来实现设置客户端信息功能,使客户端可以设置自己的名称、性别、年龄、所做省份等信息。我们暂时不管客户端如何实现,先对服务器端代码进行调整。
这里需要做以下几件事:
1.修改client_session中handle_info({tcp,Socket,Data},State) 函数对接收到的消息包的处理,使其能支持“客户端信息设置消息”。
考虑到后面还会有更多种类的消息,在这里添加一个消息路由模块message_router.erl,主要负责验证收到的消息类型和主题,并最终路由到正确的消息处理模块中去。
2.将chat_room中的客户端信息管理代码独立为一个client_manager.erl,负责具体的客户端信息处理。
3.实现将message 中type=set、subject=clientinfo 的消息路由到client_manager中,并实现更新ets表中相应的字段信息,最终回复一个消息,或广播消息给所有在线用户。
代码如下:
client_session.erl
handle_info({tcp,Socket,Data},State)-> io:format("client_session tcp data recived ~p~n",[Data]), %io:format("msg recived ~p~n",[Message]), case util_MessageParas:paraseDecode(Data) of {error,Reason}-> io:format("decode Data error ~p~n",[Reason]); {Message}-> %% we must replace "from" to avoid atack #clientinfo{id=Id}=State, NewMessage=Message#message{from=Id}, message_router:route(NewMessage) end, %NewMsg=#message{type=msg,from=State#clientinfo.id,content=Data}, %chat_room:broadCastMsg(NewMsg), {noreply,State}; 注意:在将原始json转成message后需要替换掉原来的from。
message_router.erl
%% Author: Administrator %% Created: 2012-2-28 %% Description: TODO: Add description to message_router -module(message_router). %% %% Include files %% -include("message.hrl"). %% %% Exported Functions %% -export([route/1]). %% %% API Functions %% %% %% Local Functions %% %process message %first we route by message type route(Message)when is_record(Message,message)-> #message{type=Type}=Message, case validateType(Type) of {error,Reason}-> io:format("validate message type error:~p~n",[Reason]); TheType-> #message{subject=Sub}=Message, case validateSubject(Sub) of {error,Reason}-> io:format("validate message subject error:~p~n",[Reason]); TheSub-> routeMessage(TheType,TheSub,Message) end end ; route(Els)-> io:format("message should be record:~p~n",[Els]) . %we should validate message type here validateType(Type)-> case Type of "msg"-> Type; "set"-> Type; "get"-> Type; _Els-> {error,"wrong message type"} end . validateSubject(Sub)-> case Sub of "chat"-> Sub; "uinfo"-> Sub; _Els-> {error,"wrong message subject"} end . %we will add a routing table and a call back modle later routeMessage(Type,Sub,Message)-> case Type of "msg"-> case Sub of "chat"-> chat_room:broadCastMsg(Message); _Els-> io:format("unkonw msssage subject:~p~n",[Sub]) end; "set"-> case Sub of "uinfo"-> chat_room:setUserInfo(Message); _Els-> io:format("unkonw msssage subject:~p~n",[Sub]) end; "get"-> ok; _Els-> {error,"wrong message type"} end . 注:这里只做简单的判断,后面可以考虑使用路由表的方式路由信息。
修改chat_room.erl中涉及到客户端信息操作的部分:
init([])-> id_generator:start_link(), %ets:new(clientinfo,[public, % ordered_set, % named_table, % {keypos,#clientinfo.id} % ]), client_manager:init(), {ok,#state{}} .
handle_call({remove_clientinfo,Ref},From,State)-> Key=Ref#clientinfo.id, %ets:delete(clientinfo, Key), client_manager:removeClient(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), sendMsg(Msg), {reply,ok,State} ;
handle_call({add_clientinfo,ClientInfo},From,State)-> %store clientinfo to ets %ets:insert(clientinfo, NewRec) client_manager:addClient(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 client_manager:updateClient(Message), {reply,ok,State} .
bindPid(Record,Socket)-> io:format("binding socket...~n"), case gen_tcp:controlling_process(Socket, Record#clientinfo.pid) of {error,Reason}-> io:format("binding socket...error~n"); ok -> NewRec =#clientinfo{id=Record#clientinfo.id,socket=Socket,pid=Record#clientinfo.pid}, gen_server:call(?MODULE, {add_clientinfo,NewRec}), %then we send info to clientSession to update it's State (Socket info) Pid=Record#clientinfo.pid, Pid!{bind,Socket}, io:format("clientBinded~n") end .
sendMsg(Msg)-> %case ets:lookup(clientinfo, Key)of % [Record]-> % io:format("Record found ~p~n",[Record]), % Pid=Record#clientinfo.pid, % %while send down we change msg type to dwmsg % io:format("send smg to client_session ~p~n",[Pid]), % Pid!{dwmsg,Msg}, % Next=ets:next(clientinfo, Key), % sendMsg(Next,Msg); % []-> % io:format("no clientinfo found~n") %end case client_manager:getNextClient([])of [Record]-> Next=Record#clientinfo.id, Pid=Record#clientinfo.pid, Pid!{dwmsg,Msg}, sendMsg(Msg); []-> ok end .
setUserInfo(Message)-> gen_server:call(?MODULE, {set_clientinfo,Message}) .
client_manager.erl
%% 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/0,addClient/1,updateClient/1,getNextClient/1,removeClient/1]). %% %% API Functions %% %%create clientinfo talbe here init()-> ets:new(clientinfo,[public, ordered_set, named_table, {keypos,#clientinfo.id} ]) . %% %% Local Functions %% %add new clientinfo addClient(ClientInfo)-> ets:insert(clientinfo, ClientInfo) . %update clientinfo updateClient(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 if is_record(Info,clientinfo) -> #clientinfo{nick=Nick,sex=Sex,age=Age,province=Province, city=City,posx=Px,posy=Py}=Info, ets:update_element(clientinfo, Key, [#clientinfo.nick=Nick, #clientinfo.sex=Sex, #clientinfo.age=Age, #clientinfo.province=Province, #clientinfo.city=City, #clientinfo.posx=Px, #clientinfo.posy=Py]); true-> io:format("wrong format of clientinfo"), false end . %remove clientinfo removeClient(Key)-> ets:delete(clientinfo, Key) . getNextClient(Key)-> case ets:next(clientinfo, Key) of '$end_of_table'-> []; Next-> ets:lookup(clientinfo, Next) end ; getNextClient([])-> case ets:first(clientinfo) of '$end_of_table'-> []; Key-> ets:lookup(clientinfo, Key) end .
注:上面的client_manager.erl代码中并未实现将 #message.content 转为#clientinfo 的过程,等客户端实现后再处理,这里留个尾。