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

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

首先修改客户端代码:

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

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

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

Packet.java:

  1. package com.kinglong.socket.data;
  2. import java.util.Date;
  3. import net.sf.json.JSONObject;
  4. public abstract class Packet {
  5.     String id;
  6.     String from;
  7.     String to;
  8.     String type;
  9.     String subject;
  10.     Date creationDate;
  11.     JSONObject json;
  12.     protected Packet(){
  13.     }
  14.     public Packet(JSONObject obj){
  15.         this.json=obj;
  16.     }
  17.     public String getId() {
  18.         String theid =json.getString(“id”);
  19.         if(theid==null||theid.length()==0){
  20.             return null;
  21.         }
  22.         else{
  23.             if(id!=null||theid.equals(id)){
  24.                 return id;
  25.             }
  26.             else{
  27.                 this.id=theid;
  28.                 return id;
  29.             }
  30.         }
  31.     }
  32.     public void setId(String id) {
  33.         if(id!=null){
  34.             this.id = id;
  35.         }
  36.         json.put(“id”, id);
  37.     }
  38.     public String getFrom() {
  39.         String thefrom =json.getString(“from”);
  40.         if(thefrom==null||thefrom.length()==0){
  41.             return null;
  42.         }
  43.         else{
  44.             if(from!=null||thefrom.equals(id)){
  45.                 return from;
  46.             }
  47.             else{
  48.                 this.from=thefrom;
  49.                 return from;
  50.             }
  51.         }
  52.     }
  53.     public void setFrom(String from) {
  54.         if(from!=null){
  55.             this.from = from;
  56.         }
  57.         json.put(“from”, from);
  58.     }
  59.     public String getTo() {
  60.         String theto =json.getString(“to”);
  61.         if(theto==null||theto.length()==0){
  62.             return null;
  63.         }
  64.         else{
  65.             if(to!=null||theto.equals(to)){
  66.                 return to;
  67.             }
  68.             else{
  69.                 this.to=theto;
  70.                 return to;
  71.             }
  72.         }
  73.     }
  74.     public void setTo(String to) {
  75.         if(to!=null)
  76.             this.to = to;
  77.         json.put(“to”, to);
  78.     }
  79.     public String getType() {
  80.         String thetype =json.getString(“type”);
  81.         if(thetype==null||thetype.length()==0){
  82.             return null;
  83.         }
  84.         else{
  85.             if(type!=null||thetype.equals(type)){
  86.                 return type;
  87.             }
  88.             else{
  89.                 this.type=thetype;
  90.                 return type;
  91.             }
  92.         }
  93.     }
  94.     public void setType(String type) {
  95.         if(type!=null){
  96.             this.type=type;
  97.         }
  98.         json.put(“type”, type);
  99.     }
  100.     public String getSubject() {
  101.         String thesub =json.getString(“subject”);
  102.         if(thesub==null||thesub.length()==0){
  103.             return null;
  104.         }
  105.         else{
  106.             if(subject!=null||thesub.equals(subject)){
  107.                 return subject;
  108.             }
  109.             else{
  110.                 this.subject=thesub;
  111.                 return subject;
  112.             }
  113.         }
  114.     }
  115.     public void setSubject(String subject) {
  116.         if(subject!=null){
  117.             this.subject = subject;
  118.         }
  119.         json.put(“subject”, subject);
  120.     }
  121.     public Date getCreationDate() {
  122.         Date thedate =(Date)json.get(“creationDate”);
  123.         if(thedate==null){
  124.             return null;
  125.         }
  126.         else{
  127.             if(creationDate!=null||thedate.compareTo(creationDate)==0){
  128.                 return creationDate;
  129.             }
  130.             else{
  131.                 this.creationDate=thedate;
  132.                 return creationDate;
  133.             }
  134.         }
  135.     }
  136.     public void setCreationDate(Date creationDate) {
  137.         if(creationDate!=null)
  138.           this.creationDate = creationDate;
  139.         json.put(“creationDate”, creationDate);
  140.     }
  141.     public String toJSON(){
  142.         return json.toString();
  143.     }
  144. }

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

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

  1. package com.kinglong.socket.data;
  2. import java.util.Date;
  3. import net.sf.json.JSONObject;
  4. import com.kinglong.util.MessageIdGenerator;
  5. public class Message extends Packet{
  6.     String content;
  7.     public Message(){
  8.         this.json=new JSONObject();
  9.     }
  10.     public Message(String type,String from,String to,String subject,String content){
  11.         this.json=new JSONObject();
  12.         setId(MessageIdGenerator.nextId());
  13.         setType(type);
  14.         setFrom(from);
  15.         setTo(to);
  16.         setSubject(subject);
  17.         setContent(content);
  18.         setCreationDate(new Date());
  19.     }
  20.     public Message(JSONObject obj){
  21.         this.json=obj;
  22.     }
  23.     public Message(String type){
  24.         this.json =new JSONObject();
  25.         setId(MessageIdGenerator.nextId());
  26.         setType(type);
  27.     }
  28.     public String getContent() {
  29.         String thecon =json.getString(“content”);
  30.         if(thecon==null||thecon.length()==0){
  31.             return null;
  32.         }
  33.         else{
  34.             if(content!=null||thecon.equals(content)){
  35.                 return content;
  36.             }
  37.             else{
  38.                 this.content=thecon;
  39.                 return content;
  40.             }
  41.         }
  42.     }
  43.     public void setContent(String content) {
  44.         if(content!=null)
  45.            this.content = content;
  46.         this.json.put(“content”, content);
  47.     }
  48.     public static class MessageType{
  49.         public static String MESSAGE=”msg”;
  50.     }
  51.     public static class MessageSubject{
  52.         public static String CHAT=”chat”;
  53.     }
  54. }

再修改下发送部分的代码,直接调用对象的toString()方法:

  1. public void sendMsg(Packet msg){
  2.     try {
  3.         String data=msg.toJSON();//(JSONParaser.getJSON(msg)).toString();
  4.         oust.write(data.getBytes());
  5.         oust.flush();
  6.     } catch (IOException e) {
  7.         // TODO Auto-generated catch block
  8.         e.printStackTrace();
  9.     }
  10. }

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

  1. package com.kinglong.socket.data;
  2. public class ClientInfo {
  3.     String nick;
  4.     int sex;
  5.     int age;
  6.     String province;
  7.     String city;
  8.     int posx;
  9.     int posy;
  10.     public ClientInfo(String nick,int sex,int age,String province,String city,int x,int y){
  11.         this.nick=nick;
  12.         this.sex=(sex>0?1:0);
  13.         this.age=age;
  14.         this.province=province;
  15.         this.city=city;
  16.         this.posx=x;
  17.         this.posy=y;
  18.     }
  19.     public ClientInfo(){
  20.         sex=1;//default
  21.         age=1;
  22.     }
  23.     public String getNick() {
  24.         return nick;
  25.     }
  26.     public void setNick(String nick) {
  27.         this.nick = nick;
  28.     }
  29.     public int getSex() {
  30.         return sex;
  31.     }
  32.     public void setSex(int sex) {
  33.         this.sex = (sex>0?1:0);
  34.     }
  35.     public int getAge() {
  36.         return age;
  37.     }
  38.     public void setAge(int age) {
  39.         this.age = age;
  40.     }
  41.     public String getProvince() {
  42.         return province;
  43.     }
  44.     public void setProvince(String province) {
  45.         this.province = province;
  46.     }
  47.     public String getCity() {
  48.         return city;
  49.     }
  50.     public void setCity(String city) {
  51.         this.city = city;
  52.     }
  53.     public int getPosx() {
  54.         return posx;
  55.     }
  56.     public void setPosx(int posx) {
  57.         this.posx = posx;
  58.     }
  59.     public int getPosy() {
  60.         return posy;
  61.     }
  62.     public void setPosy(int posy) {
  63.         this.posy = posy;
  64.     }
  65. }

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

  1. package com.kinglong.socket.data;
  2. import java.util.Date;
  3. import net.sf.json.JSONObject;
  4. public class SetClientInfo extends Packet {
  5.     ClientInfo info;
  6.     public SetClientInfo(){
  7.         info=new ClientInfo();
  8.         setInfo(info);
  9.         setType(“set”);
  10.         setSubject(“clientinfo”);
  11.         setCreationDate(new Date());
  12.     }
  13.     public SetClientInfo(String from,String to){
  14.         this();
  15.         setFrom(from);
  16.         setTo(to);
  17.         setType(“set”);
  18.         setSubject(“clientinfo”);
  19.         setCreationDate(new Date());
  20.     }
  21.     public SetClientInfo(ClientInfo info){
  22.         this.info=info;
  23.         this.json=new JSONObject();
  24.         setInfo(info);
  25.         setType(“set”);
  26.         setSubject(“clientinfo”);
  27.         setCreationDate(new Date());
  28.     }
  29.     public ClientInfo getInfo() {
  30.         ClientInfo theinfo =(ClientInfo)json.get(“content”);
  31.         if(theinfo==null){
  32.             return null;
  33.         }
  34.         else{
  35.             if(info!=null||theinfo.equals(info)){
  36.                 return info;
  37.             }
  38.             else{
  39.                 this.info=theinfo;
  40.                 return info;
  41.             }
  42.         }
  43.     }
  44.     public void setInfo(ClientInfo info) {
  45.         if(info!=null)
  46.            this.info = info;
  47.         json.put(“content”, info);
  48.     }
  49. }

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

下面修改服务器端:

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

  1. para({“content”,Val},Data)->
  2.     io:format(“para content:~p~n”,[Data]),
  3.     NewData=Data#message{content=Val},
  4.     io:format(“paraed content:~p~n”,[NewData]),
  5.     NewData
  6. ;

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

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

  1. %% Author: Administrator
  2. %% Created: 2012-3-1
  3. %% Description: TODO: Add description to util_SetInfoParas
  4. -module(util_SetInfoParas).
  5. %%
  6. %% Include files
  7. %%
  8. -include(“clientinfo.hrl”).
  9. %%
  10. %% Exported Functions
  11. %%
  12. -export([paraElements/1]).
  13. %%
  14. %% API Functions
  15. %%
  16. %%
  17. %% Local Functions
  18. %%
  19. paraElements(Obj)->
  20.     {obj,List}=Obj,
  21.     Data =#clientinfo{},
  22.     %catch exception here
  23.     io:format(“data list is:~p~n”,[List]),
  24.     try paraEle(List,Data)
  25.     catch
  26.         {error,Reason,NewData}->
  27.             {error,Reason,NewData}
  28.     end
  29. .
  30. paraEle([Ele|Els],Data)->
  31.     io:format(“ele is:~p~n”,[Ele]),
  32.     NewData=para(Ele,Data),
  33.     paraEle(Els,NewData)
  34. ;
  35. paraEle([],Data)->
  36.     Data
  37. .
  38. para({“age”,Val},Data)->
  39.     io:format(“para age:~p~n”,[Data]),
  40.     NewData=Data#clientinfo{age=Val},
  41.     io:format(“paraed content:~p~n”,[NewData]),
  42.     NewData
  43. ;
  44. para({“city”,Val},Data)->
  45.     io:format(“para age:~p~n”,[Data]),
  46.     NewData=Data#clientinfo{city=binary_to_list(Val)},
  47.     io:format(“paraed content:~p~n”,[NewData]),
  48.     NewData
  49. ;
  50. para({“nick”,Val},Data)->
  51.     io:format(“para age:~p~n”,[Data]),
  52.     NewData=Data#clientinfo{nick=binary_to_list(Val)},
  53.     io:format(“paraed content:~p~n”,[NewData]),
  54.     NewData
  55. ;
  56. para({“posx”,Val},Data)->
  57.     io:format(“para age:~p~n”,[Data]),
  58.     NewData=Data#clientinfo{posx=Val},
  59.     io:format(“paraed content:~p~n”,[NewData]),
  60.     NewData
  61. ;
  62. para({“posy”,Val},Data)->
  63.     io:format(“para age:~p~n”,[Data]),
  64.     NewData=Data#clientinfo{posy=Val},
  65.     io:format(“paraed content:~p~n”,[NewData]),
  66.     NewData
  67. ;
  68. para({“province”,Val},Data)->
  69.     io:format(“para age:~p~n”,[Data]),
  70.     NewData=Data#clientinfo{province=binary_to_list(Val)},
  71.     io:format(“paraed content:~p~n”,[NewData]),
  72.     NewData
  73. ;
  74. para({“sex”,Val},Data)->
  75.     io:format(“para age:~p~n”,[Data]),
  76.     NewData=Data#clientinfo{sex=Val},
  77.     io:format(“paraed content:~p~n”,[NewData]),
  78.     NewData
  79. ;
  80. para({Key,Val},Data)->
  81.     io:format(“decode key is:~p~n”,[Key]),
  82.     io:format(“decode Val is:~p~n”,[Val]),
  83.     %no mache
  84.     %throw exception
  85.     throw({error,”unkown element”,Data})
  86. .

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

client_manager.erl:

  1. %update clientinfo
  2. updateClient(Message)->
  3.     #message{content=Info,from=Key}=Message,
  4.     io:format(“content of message is:~p~n”,[Info]),
  5.     %TODO:we should formart content from json to record #clientinfo
  6.     Rec =util_SetInfoParas:paraElements(Info),
  7.     io:format(“parased record is:~p~n”,[Rec]),
  8.     if is_record(Rec,clientinfo) ->
  9.            #clientinfo{nick=Nick,sex=Sex,age=Age,province=Province,
  10.                        city=City,posx=Px,posy=Py}=Rec,
  11.            io:format(“here Key is:~p~n”,[Key]),
  12.            case ets:update_element(clientinfo, Key, [{#clientinfo.nick,Nick},
  13.                                                 {#clientinfo.sex,Sex},
  14.                                                 {#clientinfo.age,Age},
  15.                                                 {#clientinfo.province,Province},
  16.                                                 {#clientinfo.city,City},
  17.                                                 {#clientinfo.posx,Px},
  18.                                                 {#clientinfo.posy,Py}]) of
  19.                 true->
  20.                     {ok,Rec};
  21.                 false->
  22.                     {fals,Rec}
  23.           end;
  24.        true->
  25.         io:format(“wrong format of clientinfo”),
  26.         {false,Rec}
  27.     end
  28. .

更新成功后,会将此消息广播给所有的在线用户:

chat_room.erl

  1. handle_call({set_clientinfo,Message},From,State)->
  2.     %we can send result message to client
  3.     %or send a broadcast message to all client
  4.     case client_manager:updateClient(Message)of
  5.         {ok,Rec}->
  6.             #message{from=Id}=Message,
  7.             #clientinfo{nick=Nick}=Rec,
  8.             %Content=["client",integer_to_list(Id),"has change nickname:"|Nick],
  9.             %[Cont]=["client"|integer_to_list(Id)],
  10.             %io:format(“Cont is:~p~n”,[Cont]),
  11.             %[Cont1]=[Cont|"has change nickname:"],
  12.             %io:format(“Cont1 is:~p~n”,[Cont1]),
  13.             %[Content]=[Cont1|Nick],
  14.             %io:format(“Content is:~p~n”,[Content]),
  15.             {Content}={“clinet” ++ integer_to_list(Id) ++ ” has change nickname:” ++ Nick},
  16.             NewMessage=#message{id=”0″,
  17.                              from=Id,
  18.                              to=”",
  19.                              content=Content,
  20.                              type=”msg”,
  21.                              subject=”chat”,
  22.                              time=Message#message.time},
  23.             io:format(“Notice Message is:~p~n”,[NewMessage]),
  24.             sendMsg(NewMessage,[]);
  25.         {false,Rec}->
  26.             ok
  27.     end,
  28.     {reply,ok,State}
  29. .

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

  1. %parase message to json
  2. paraseEncode(Message)->
  3.     io:format(“Encoding Message:~p~n”,[Message]),
  4.     {message,Id,Type,From,To,Subject,Content,Time}=Message,
  5.     {time,Date,Day,Hours,Minutes,Month,Seconds,TheTime,Offset,Year}=Time,
  6.     %Data={obj,[{"content",list_to_binary(Content)},
  7.      TheContent= if is_list(Content)->
  8.                         list_to_binary(Content);
  9.                     true ->
  10.                       Content
  11.                  end,
  12.      Data={obj,[{"content",TheContent},
  13.               {"from",list_to_binary(From)},
  14.               {"to",list_to_binary(To)},
  15.               {"subject",list_to_binary(Subject)},
  16.               {"id",list_to_binary(Id)},
  17.               {"type",list_to_binary(Type)},
  18.               {"creationDate",{obj,[{"date",Date},
  19.                                {"day",Day},
  20.                                {"hours",Hours},
  21.                                {"minutes",Minutes},
  22.                                {"month",Month},
  23.                                {"seconds",Seconds},
  24.                                {"time",TheTime},
  25.                                {"timezoneOffset",Offset},
  26.                                {"year",Year}
  27.                                 ]
  28.                               }
  29.               }]
  30.           },
  31.    rfc4627:encode(Data)
  32. .

 

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

  1. @Override
  2.     public void run() {
  3.         // TODO Auto-generated method stub
  4.         InputStreamReader reader = new InputStreamReader(inst);
  5.         BufferedReader bfreader = new BufferedReader(reader);
  6.         while(isrunning){
  7.             String str=null;
  8.             try {
  9.                 byte[] data =new byte[300];
  10.                 int len =0;
  11.                 while((len=inst.read(data))>0){
  12.                     str=new String(data).trim();
  13.                     Iterator<JSONObject> it =JSONParaser.getString(str);
  14.                     while(it.hasNext()){
  15.                         JSONObject obj=it.next();
  16.                         if(“msg”.equals(obj.get(“type”))){
  17.                             Message msg =(Message)JSONObject.toBean(obj,Message.class);
  18.                             mf.recMsg(msg);
  19.                         }
  20.                     }
  21.                 }
  22.             } catch (IOException e) {
  23.                 // TODO Auto-generated catch block
  24.                 System.out.println(“recMsg error”+e.getMessage());
  25.                 isrunning=false;
  26.             }
  27.             try {
  28.                 Thread.sleep(500);
  29.             } catch (InterruptedException e) {
  30.                 // TODO Auto-generated catch block
  31.                 e.printStackTrace();
  32.             }
  33.         }
  34.     }

测试结果如下:

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

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

  1. getNick(Key)->
  2.     case ets:lookup(clientinfo, Key) of
  3.         [Record]->
  4.             #clientinfo{nick=Nick}=Record,
  5.             case Nick of
  6.                 undefined ->
  7.                     ”client”++integer_to_list(Key);
  8.                 Nick->
  9.                      Nick
  10.             end;
  11.         []->
  12.             ”client”++integer_to_list(Key)
  13.     end
  14. .

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

  1. handle_call({set_clientinfo,Message},From,State)->
  2.     %we can send result message to client
  3.     %or send a broadcast message to all client
  4.     #message{from=Id}=Message,
  5.     [Client]=client_manager:getClient(Id),
  6.     #clientinfo{pid=Pid}=Client,
  7.     TheNick=client_manager:getNick(Id),
  8.     case client_manager:updateClient(Message)of
  9.         {ok,Rec}->
  10.             #clientinfo{nick=Nick}=Rec,
  11.             %Content=["client",integer_to_list(Id),"has change nickname:"|Nick],
  12.             %[Cont]=["client"|integer_to_list(Id)],
  13.             %io:format(“Cont is:~p~n”,[Cont]),
  14.             %[Cont1]=[Cont|"has change nickname:"],
  15.             %io:format(“Cont1 is:~p~n”,[Cont1]),
  16.             %[Content]=[Cont1|Nick],
  17.             %io:format(“Content is:~p~n”,[Content]),
  18.             Pid!{setinfo,Rec},
  19.             {Content}={TheNick ++ ” has change nickname:” ++ Nick},
  20.                NewMessage=#message{id=”0″,
  21.                              from=Id,
  22.                              to=”",
  23.                              content=Content,
  24.                              type=”msg”,
  25.                              subject=”chat”,
  26.                              time=Message#message.time},
  27.             io:format(“Notice Message is:~p~n”,[NewMessage]),
  28.             sendMsg(NewMessage,[]);
  29.         {false,Rec}->
  30.             ok
  31.     end,
  32.     {reply,ok,State}
  33. .

再修改client_session中下发时的from:

  1. handle_info({dwmsg,Message},State)->
  2.     %here we should set from back
  3.     io:format(“client_session dwmsg state is ~p~n”,[State]),
  4.     #message{from=Id}=Message,
  5.     Nick=client_manager:getNick(Id),
  6.     NewMessage =Message#message{from=Nick},
  7.     JSON=util_MessageParas:paraseEncode(NewMessage),
  8.     io:format(“client_session dwmsg recived ~p~n”,[JSON]),
  9.     case gen_tcp:send(State#clientinfo.socket, JSON)of
  10.         ok->
  11.             io:format(“client_session dwmsg sended ~n”);
  12.         {error,Reason}->
  13.             io:format(“client_session dwmsg sended error ~p ~n”,Reason)
  14.     end,
  15.     {noreply,State};

测试效果如下:



发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

(Spamcheck Enabled)