接下来实现设置客户端信息功能,使客户端可以设置自己的名称、性别、年龄、所做省份等信息。我们暂时不管客户端如何实现,先对服务器端代码进行调整。
这里需要做以下几件事:
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 的过程,等客户端实现后再处理,这里留个尾。