Erlang 聊天室程序(五) 设置客户端信息

            接下来实现设置客户端信息功能,使客户端可以设置自己的名称、性别、年龄、所做省份等信息。我们暂时不管客户端如何实现,先对服务器端代码进行调整。

            这里需要做以下几件事:

            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 的过程,等客户端实现后再处理,这里留个尾。


展开阅读全文

没有更多推荐了,返回首页