上篇开了个头编写了基本的框架,这次连同客户端服务器端代码一起完善下。
首先修改客户端代码:
之前在数据交换部分,客户端中定义了一个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};
测试效果如下: