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

           上篇开了个头编写了基本的框架,这次连同客户端服务器端代码一起完善下。

           首先修改客户端代码:

           之前在数据交换部分,客户端中定义了一个Message bean类,里面包含了发送一条消息所需要的基本信息,包括id、type、subject、from、to、content等。但这里的

content是一个String 类型,如果要表示更复杂的消息就不太适用了。

           由于所有的消息id、type、subject、from、to 这几个成员的类型是确定的,对应的操作方法也是固定的,所以我们抽象出一个抽象类:Packet 用来表示交互中的所有消息:

Packet.java:

package com.kinglong.socket.data;

import java.util.Date;

import net.sf.json.JSONObject;

public abstract class Packet {

	String id;
	String from;
	String to;
	String type;
	String subject;
	Date creationDate;
	JSONObject json;
	
	protected Packet(){
	}
	
	public Packet(JSONObject obj){
		this.json=obj;
	}

	public String getId() {
		String theid =json.getString("id");
		if(theid==null||theid.length()==0){
			return null;
		}
		else{
			if(id!=null||theid.equals(id)){
				return id;
			}
			else{
				this.id=theid;
				return id;
			}			
		}		
	}

	public void setId(String id) {
		if(id!=null){
			this.id = id;
		}
		json.put("id", id);
	}

	public String getFrom() {
		String thefrom =json.getString("from");
		if(thefrom==null||thefrom.length()==0){
			return null;
		}
		else{
			if(from!=null||thefrom.equals(id)){
				return from;
			}
			else{
				this.from=thefrom;
				return from;
			}			
		}
	}

	public void setFrom(String from) {
		if(from!=null){
			this.from = from;
		}
		json.put("from", from);
	}

	public String getTo() {
		String theto =json.getString("to");
		if(theto==null||theto.length()==0){
			return null;
		}
		else{
			if(to!=null||theto.equals(to)){
				return to;
			}
			else{
				this.to=theto;
				return to;
			}			
		}		
	}

	public void setTo(String to) {
		if(to!=null)
			this.to = to;
		json.put("to", to);
	}

	public String getType() {
		String thetype =json.getString("type");
		if(thetype==null||thetype.length()==0){
			return null;
		}
		else{
			if(type!=null||thetype.equals(type)){
				return type;
			}
			else{
				this.type=thetype;
				return type;
			}			
		}	
	}

	public void setType(String type) {
		if(type!=null){
			this.type=type;
		}
		json.put("type", type);
	}

	public String getSubject() {
		String thesub =json.getString("subject");
		if(thesub==null||thesub.length()==0){
			return null;
		}
		else{
			if(subject!=null||thesub.equals(subject)){
				return subject;
			}
			else{
				this.subject=thesub;
				return subject;
			}			
		}
	}

	public void setSubject(String subject) {
		if(subject!=null){
			this.subject = subject;
		}
		json.put("subject", subject);		
	}
		
	
	public Date getCreationDate() {
		Date thedate =(Date)json.get("creationDate");
		if(thedate==null){
			return null;
		}
		else{
			if(creationDate!=null||thedate.compareTo(creationDate)==0){
				return creationDate;
			}
			else{
				this.creationDate=thedate;
				return creationDate;
			}			
		}		
	}

	public void setCreationDate(Date creationDate) {
		if(creationDate!=null)
		  this.creationDate = creationDate;
		json.put("creationDate", creationDate);
	}

	public String toJSON(){
		return json.toString();
	}
}
       与之前不同的是这次将Bean解析为JSON String的任务交给Bean本身。可以看到以上代码中并没有定义content的实现,所以具体的消息可根据自身需要来定制。

       根据这个思想,改造普通消息类Message:

      

package com.kinglong.socket.data;

import java.util.Date;

import net.sf.json.JSONObject;

import com.kinglong.util.MessageIdGenerator;

public class Message extends Packet{
	String content;
	public Message(){	
		this.json=new JSONObject();
	}

	public Message(String type,String from,String to,String subject,String content){
		this.json=new JSONObject();
		setId(MessageIdGenerator.nextId());
		setType(type);
		setFrom(from);
		setTo(to);
		setSubject(subject);
		setContent(content);
		setCreationDate(new Date());
	}

	public Message(JSONObject obj){
		this.json=obj;
	}
	
	public Message(String type){
		this.json =new JSONObject();
		setId(MessageIdGenerator.nextId());
		setType(type);
	}
		
	public String getContent() {
		String thecon =json.getString("content");
		if(thecon==null||thecon.length()==0){
			return null;
		}
		else{
			if(content!=null||thecon.equals(content)){
				return content;
			}
			else{
				this.content=thecon;
				return content;
			}			
		}
	}

	public void setContent(String content) {
		if(content!=null)
		   this.content = content;
		this.json.put("content", content);
	}

	public static class MessageType{
		public static String MESSAGE="msg";
	}
	
	public static class MessageSubject{
		public static String CHAT="chat";
	}
}
             再修改下发送部分的代码,直接调用对象的toString()方法:

	public void sendMsg(Packet msg){
		try {					
			String data=msg.toJSON();//(JSONParaser.getJSON(msg)).toString();
			oust.write(data.getBytes());
			oust.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

         新建一个类ClientInfo 表示客户端要设置的信息:

package com.kinglong.socket.data;

public class ClientInfo {
	String nick;
	int sex;
	int age;
	String province;
	String city;
	int posx;
	int posy;
	
	public ClientInfo(String nick,int sex,int age,String province,String city,int x,int y){
		this.nick=nick;
		this.sex=(sex>0?1:0);
		this.age=age;
		this.province=province;
		this.city=city;
		this.posx=x;
		this.posy=y;
	}
	
	public ClientInfo(){
		sex=1;//default
		age=1;
	}

	public String getNick() {
		return nick;
	}

	public void setNick(String nick) {
		this.nick = nick;
	}

	public int getSex() {
		return sex;
	}

	public void setSex(int sex) {
		this.sex = (sex>0?1:0);
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getProvince() {
		return province;
	}

	public void setProvince(String province) {
		this.province = province;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public int getPosx() {
		return posx;
	}

	public void setPosx(int posx) {
		this.posx = posx;
	}

	public int getPosy() {
		return posy;
	}

	public void setPosy(int posy) {
		this.posy = posy;
	}
}

             新建设置客户端消息类:SetClientInfo继承Packet类

package com.kinglong.socket.data;

import java.util.Date;

import net.sf.json.JSONObject;

public class SetClientInfo extends Packet {
	ClientInfo info;
	
	public SetClientInfo(){
		info=new ClientInfo();
		setInfo(info);
		setType("set");
		setSubject("clientinfo");	
		setCreationDate(new Date());
	}
	
	public SetClientInfo(String from,String to){
		this();
		setFrom(from);
		setTo(to);
		setType("set");
		setSubject("clientinfo");
		setCreationDate(new Date());
	}

	public SetClientInfo(ClientInfo info){
		this.info=info;
		this.json=new JSONObject();
		setInfo(info);
		setType("set");
		setSubject("clientinfo");	
		setCreationDate(new Date());
	}
	


	public ClientInfo getInfo() {
		ClientInfo theinfo =(ClientInfo)json.get("content");
		if(theinfo==null){
			return null;
		}
		else{
			if(info!=null||theinfo.equals(info)){
				return info;
			}
			else{
				this.info=theinfo;
				return info;
			}			
		}		
	}

	public void setInfo(ClientInfo info) {
		if(info!=null)
		   this.info = info;
		json.put("content", info);
	}	
}

           可以看到这里将content 设置为了刚才的ClientInfo 对象。

           下面修改服务器端:

           先要去掉JSON数据解析时的is_binary判断,因为以后发送的消息content里不一定就是binary了。

para({"content",Val},Data)->
	io:format("para content:~p~n",[Data]),	
	NewData=Data#message{content=Val},
	io:format("paraed content:~p~n",[NewData]),
	NewData
;

            再修改client_manager.erl 中更改客户端信息部分的代码,将content中的json数据转成#clientinfo,再更新到数据表中去。

            为此新建一个专门的模块util_SetInfoParas.erl处理setClientInfo消息:

%% Author: Administrator
%% Created: 2012-3-1
%% Description: TODO: Add description to util_SetInfoParas
-module(util_SetInfoParas).

%%
%% Include files
%%
-include("clientinfo.hrl").
%%
%% Exported Functions
%%
-export([paraElements/1]).

%%
%% API Functions
%%



%%
%% Local Functions
%%
paraElements(Obj)->
	{obj,List}=Obj,
	Data =#clientinfo{},
	%catch exception here
	io:format("data list is:~p~n",[List]),
	try paraEle(List,Data)		
	catch
		{error,Reason,NewData}->
			{error,Reason,NewData}
	end
.

paraEle([Ele|Els],Data)->
	io:format("ele is:~p~n",[Ele]),
	NewData=para(Ele,Data),
	paraEle(Els,NewData)
;
paraEle([],Data)->
	Data
.

para({"age",Val},Data)->
	io:format("para age:~p~n",[Data]),	
	NewData=Data#clientinfo{age=Val},
	io:format("paraed content:~p~n",[NewData]),
	NewData
;
para({"city",Val},Data)->
	io:format("para age:~p~n",[Data]),	
	NewData=Data#clientinfo{city=binary_to_list(Val)},
	io:format("paraed content:~p~n",[NewData]),
	NewData
;
para({"nick",Val},Data)->
	io:format("para age:~p~n",[Data]),	
	NewData=Data#clientinfo{nick=binary_to_list(Val)},
	io:format("paraed content:~p~n",[NewData]),
	NewData
;
para({"posx",Val},Data)->
	io:format("para age:~p~n",[Data]),	
	NewData=Data#clientinfo{posx=Val},
	io:format("paraed content:~p~n",[NewData]),
	NewData
;
para({"posy",Val},Data)->
	io:format("para age:~p~n",[Data]),	
	NewData=Data#clientinfo{posy=Val},
	io:format("paraed content:~p~n",[NewData]),
	NewData
;
para({"province",Val},Data)->
	io:format("para age:~p~n",[Data]),	
	NewData=Data#clientinfo{province=binary_to_list(Val)},
	io:format("paraed content:~p~n",[NewData]),
	NewData
;
para({"sex",Val},Data)->
	io:format("para age:~p~n",[Data]),	
	NewData=Data#clientinfo{sex=Val},
	io:format("paraed content:~p~n",[NewData]),
	NewData
;
para({Key,Val},Data)->
	io:format("decode key is:~p~n",[Key]),
	io:format("decode Val is:~p~n",[Val]),
	%no mache
    %throw exception
	throw({error,"unkown element",Data})
.



           接着在更新数据库前调用以上代码:

           client_manager.erl:

%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
	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(clientinfo, 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
.
              更新成功后,会将此消息广播给所有的在线用户:

chat_room.erl

handle_call({set_clientinfo,Message},From,State)->
	%we can send result message to client
	%or send a broadcast message to all client 
	case client_manager:updateClient(Message)of
		{ok,Rec}->
			#message{from=Id}=Message,
			#clientinfo{nick=Nick}=Rec,			
			%Content=["client",integer_to_list(Id),"has change nickname:"|Nick],
			%[Cont]=["client"|integer_to_list(Id)],
			%io:format("Cont is:~p~n",[Cont]),
			%[Cont1]=[Cont|"has change nickname:"],
			%io:format("Cont1 is:~p~n",[Cont1]),
			%[Content]=[Cont1|Nick],
			%io:format("Content is:~p~n",[Content]),
			{Content}={"clinet" ++ integer_to_list(Id) ++ " 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(NewMessage,[]);
		{false,Rec}->
            ok
    end,
	{reply,ok,State}
.

      最后需要修改util_MessageParas.erl中的JSON编码部分,判断如果要发送给客户端的Message消息内容是list的话才转成相应的字符串。

%parase message to json
paraseEncode(Message)->
	io:format("Encoding Message:~p~n",[Message]),
	{message,Id,Type,From,To,Subject,Content,Time}=Message,
	{time,Date,Day,Hours,Minutes,Month,Seconds,TheTime,Offset,Year}=Time,
	%Data={obj,[{"content",list_to_binary(Content)},
     TheContent= if is_list(Content)->
						list_to_binary(Content);
					true ->
					  Content
                 end,
     Data={obj,[{"content",TheContent},
		  	  {"from",list_to_binary(From)},
		  	  {"to",list_to_binary(To)},
		  	  {"subject",list_to_binary(Subject)},
		  	  {"id",list_to_binary(Id)},
		      {"type",list_to_binary(Type)},
		 	  {"creationDate",{obj,[{"date",Date},
							   {"day",Day},
							   {"hours",Hours},
						       {"minutes",Minutes},
							   {"month",Month},
							   {"seconds",Seconds},
							   {"time",TheTime},
							   {"timezoneOffset",Offset},
							   {"year",Year}
								]
							  }
			  }]
		  },
   rfc4627:encode(Data)
.


        再修改客户端收到消息后的解析代码:

@Override
	public void run() {
		// TODO Auto-generated method stub
		InputStreamReader reader = new InputStreamReader(inst);
		BufferedReader bfreader = new BufferedReader(reader);
		while(isrunning){
			String str=null;
			try {
				byte[] data =new byte[300];
				int len =0;
				while((len=inst.read(data))>0){					
					str=new String(data).trim();
					Iterator<JSONObject> it =JSONParaser.getString(str);
					while(it.hasNext()){
						JSONObject obj=it.next();
						if("msg".equals(obj.get("type"))){
							Message msg =(Message)JSONObject.toBean(obj,Message.class);
							mf.recMsg(msg);
						}												
					}																	
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				System.out.println("recMsg error"+e.getMessage());
				isrunning=false;
			}
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}			

           测试结果如下:


另外,既然能够设置客户端的昵称了,那么就再实现下发消息时from的替换吧:

先为client_manager添加获取客户端昵称功能,获取不到或undfined就取默认的:

getNick(Key)->
    case ets:lookup(clientinfo, 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
.

再修改设置个人信息后的通知消息:

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,
    [Client]=client_manager:getClient(Id),
    #clientinfo{pid=Pid}=Client,
    TheNick=client_manager:getNick(Id),    
    case client_manager:updateClient(Message)of
        {ok,Rec}->            
            #clientinfo{nick=Nick}=Rec,            
            %Content=["client",integer_to_list(Id),"has change nickname:"|Nick],
            %[Cont]=["client"|integer_to_list(Id)],
            %io:format("Cont is:~p~n",[Cont]),
            %[Cont1]=[Cont|"has change nickname:"],
            %io:format("Cont1 is:~p~n",[Cont1]),
            %[Content]=[Cont1|Nick],
            %io:format("Content is:~p~n",[Content]),
            
            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(NewMessage,[]);
        {false,Rec}->
            ok
    end,
    {reply,ok,State}
.
再修改client_session中下发时的from:

handle_info({dwmsg,Message},State)->
    %here we should set from back
    io:format("client_session dwmsg state is ~p~n",[State]),
    #message{from=Id}=Message,
    Nick=client_manager:getNick(Id),    
    NewMessage =Message#message{from=Nick},
    JSON=util_MessageParas:paraseEncode(NewMessage),
    io:format("client_session dwmsg recived ~p~n",[JSON]),
    case gen_tcp:send(State#clientinfo.socket, JSON)of
        ok->
            io:format("client_session dwmsg sended ~n");
        {error,Reason}->    
            io:format("client_session dwmsg sended error ~p ~n",Reason)
    end,
    {noreply,State};

测试效果如下:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值