1.消息中转
th项目利用交换服务器为跨服务器通信提供消息中转服务.
交换服务器负责:
.接受服务器登记
.登记信息保存在mysql中,以便所有服务器共享访问.
.负责转发
.处理有限的协议
实现时以用户上线通知为例。
用户A在S1服务器上登录,需要把该用户上线的消息发送给在其它服务器登录的同事和好友.
S1确定用户A的同事和好友信息:
。在S1上登录的
。在其它服务器上登录的
转发消息包含2部分:消息本身以及目标信息。
目标信息用于转发处理,包括服务器行集,而服务器记录包含用户行集。
转发消息(905-Request)
。交换服务器转发的消息
。目标为交换服务器(3,0)
。原始消息的目标设置为(0,0):对于接收的客户端而言,未指定目标就是客户端自己
。服务器行集,每条记录为该服务器上的用户行集
转发消息(906-Request)
。应用服务器转发的消息
。目标为服务器
。用户行集:记录该服务器上需要发送的客户端
当一个消息需要发送给跨多个服务器上的用户时,需要通过交换服务器中转.
IIM接口的Send(CMsg *msg,USER_LIST &user_list)方法为此提供统一的入口.
避免消息多个副本.
只向交换服务器发送一个用户消息转发请求(905-Request).消息中包含了需要转发的服务器列表和每个服务器上的用户,服务器和用户都包含了通道信息.
交换服务器和接收服务器直接利用通道信息发送.
交换服务器把消息(906-Request)发送给目标服务器.目标服务器接收后还原原始消息发送给客户端.
转发消息具有和原始消息相同的时序控制属性.
。多目标用户消息发送:
通过IIM接口为其它插件使用.
取得user_list可优化,查询时已经得到服务器上用户的分布和通道信息,提高性能.
int CIMPlugin::Send(CMsg *msg,USER_LIST &user_list) {
CQQ_SERVERID this_svr_id = mem_data_->GetLocalServer()->GetServerID();
CWrappedMsg<> *pwm = new CWrappedMsg<>;
pwm->msg = msg;
CAutoMap<CQQ_SERVERID,vector<IUSERINFO*>* > svr_list; ///< 需要经由转发的服务器
USER_LIST::iterator user_iter = user_list.begin();
for(; user_iter != user_list.end(); user_iter++) {
IUSERINFO *user = *user_iter;
if (user->GetSvrID()==this_svr_id) { ///< 登录在当前服务器上的用户
if((msg->GetMsgAttr()&EXTEND1_MASK)||user->IsReady()){
pwm->connid.push_back(user->GetHandle());
}
}
else {
///< 需要转发的消息
CQQ_SERVERID svr_id = user->GetSvrID();
CAutoMap<CQQ_SERVERID,vector<IUSERINFO*>* >::iterator iter = svr_list.find(svr_id);
if (iter==svr_list.end()) {
svr_list.insert(make_pair(svr_id,new vector<IUSERINFO*>));
iter = svr_list.find(svr_id);
}
iter->second->push_back(user);
}
}
if (!svr_list.empty()) {
CMsg *tmsg = msg->Clone();
tmsg->SetDest64(0,0);
///< 封装消息,发送给交换服务器
CMsg *nmsg = new CMsg;
if (msg->IsSeqCtrl()) {
nmsg->SetMsgAttr(SEQ_CTRL_MASK);
nmsg->SetSeqCtrlKeyValue(msg->GetSeqCtrlKeyValue());
}
nmsg->SetMsgType(MT_REQUEST);
nmsg->SetMsgID(905);
nmsg->SetSource(4,this_svr_id);
nmsg->SetDest(3,0); ///<
CUMXHelper umxhelper;
umxhelper.Attach(tmsg);
umxhelper.Serialize();
char *buffer = umxhelper.GetBuffer(true);
unsigned long len = umxhelper.GetLength();
nmsg->AddParam("msg",buffer,len);
CRowset *rs = new CRowset;
rs->SetFields("svr_id","handle","user_list",0);
nmsg->AddRowset(rs);
FieldDescriptor *fd3 = rs->GetFieldInfo(2);
fd3->type = dtLongRowset;
CAutoMap<CQQ_SERVERID,vector<IUSERINFO*>* >::iterator iter = svr_list.begin();
int k = 0;
while(iter!=svr_list.end()) {
CQQ_SERVERID svrid = iter->first;
ISERVERINFO *server = mem_data_->GetServer(svrid);
if (server==0) {
iter++;
continue;
}
vector<IUSERINFO*> *v = iter->second;
rs->AddRecord();
char sz_svr_id[10],sz_handle[16];
sprintf(sz_svr_id,"%lu",svrid);
sprintf(sz_handle,"%lu",server->GetHandle());
rs->SetFieldValue(k,0,sz_svr_id);
rs->SetFieldValue(k,1,sz_handle);
CDataBlock *data = new CDataBlock;
data->type_ = dtLongRowset;
CRowset *nrs = new CRowset;
nrs->SetFields("orgid","userid","handle",0);
vector<IUSERINFO*>::iterator it = v->begin();
while(it!=v->end()) {
IUSERINFO *user = *it;
char sz_orgid[16],sz_user_id[16],sz_handle[16];
sprintf(sz_orgid,"%lu",user->GetOrgID());
sprintf(sz_user_id,"%lu",user->GetUserSerial());
sprintf(sz_handle,"%lu",user->GetHandle());
nrs->AddRecord(sz_orgid,sz_user_id,sz_handle,0);
it++;
}
data->AttachRowset(nrs);
rs->SetFieldValue(k,2,data);
iter++;
}
network_->SendMsg(nmsg);
}
if (pwm->connid.size()==0) {
delete pwm;
}
else
SendMsg(pwm);
return 0;
}
。交换服务器中转
int CXSwitch::OnForwardMsg(CWrappedMsg<> *in,vector<CWrappedMsg<> *> &out,DISPATCH_RESULT &or) {
or.auto_resp_ = false;
CMsg *msg = in->msg;
char *buffer = 0;
unsigned long len = 0;
msg->GetParam("msg",&buffer,len);
if (buffer==0) {
return -1;
}
CRowset *rs = msg->GetRowset(0);
if (rs==0)
return -1;
int row = rs->GetRSMeta(RST_ROW_CNT);
if (row==0)
return 0;
for (int i=0;i<row;i++) {
const char *svr_id = rs->GetFieldValue(i,0);
const char *handle = rs->GetFieldValue(i,1);
CDataBlock *data = 0;
rs->GetFieldValue(i,2,&data);
if (data==0)
continue;
CRowset *prs = data->rs_;
if (prs==0)
continue;
CWrappedMsg<> *pwm = new CWrappedMsg<>;
CMsg *nmsg = new CMsg;
nmsg->SetMsgType(MT_REQUEST);
nmsg->SetMsgID(906);
nmsg->AddParam("msg",buffer,len);
nmsg->AddRowset(prs);
data->AttachRowset(0);
nmsg->SetMsgAttr(SEQ_CTRL_MASK);
nmsg->SetSeqCtrlKeyValue(msg->GetSeqCtrlKeyValue());
pwm->msg = nmsg;
pwm->connid.push_back(atol(handle));
network_->SendMsg(pwm);
}
return 0;
}
。应用服务器中转
int CIMPlugin::OnForwardMsg(CWrappedMsg<> *in,vector<CWrappedMsg<> *> &out,DISPATCH_RESULT &or) {
or.auto_resp_ = false;
CMsg *msg = in->msg;
char *buffer = 0;
unsigned long len = 0;
msg->GetParam("msg",&buffer,len);
if (buffer==0) {
return -1;
}
CUMXHelper umxhelper;
int ret = umxhelper.Structuralize(buffer,len);
if (ret) {
return -1;
}
CMsg *tmsg = umxhelper.PopMsg();
CRowset *rs = msg->GetRowset(0);
if (rs==0)
return -1;
int row = rs->GetRSMeta(RST_ROW_CNT);
if (row==0)
return 0;
CWrappedMsg<> *pwm = new CWrappedMsg<>;
pwm->msg = tmsg;
for (int i=0;i<row;i++) {
const char *sz_handle = rs->GetFieldValue(i,2);
CHANNEL_ID handle = atol(sz_handle);
pwm->connid.push_back(handle);
}
network_->SendMsg(pwm);
return 0;
}
2.备忘
2.1用户状态信息
用户状态运行时动态改变,需要反映到同事和好友的客户端上。
短时间的不一致不是问题,但必须有一种机制保证最终可以处于一致的状态.
通知消息的多段传输过程中,由于进程或网络原因,消息不一定会发送到用户的所有在线好友和同事.
数据库中记录的用户状态时准确的,新上线的同事和好友所得到的用户状态是正确的.
有2种解决方法:
。利用以内存数据库作为存储的SEMQ:在发送服务器和接受服务器之间确认,相关信息全部保存在内存中.
服务器负责自己写入的记录,重启时删除历史记录.
。利用事件中心:这是后话. 利用内存表作为存储可提高性能.
或者等待下一次用户状态改变. ***目前没有对此进行处理.
2.2聊天消息
从可靠性角度,在线聊天的消息也应该写入数据库后再发送,由SEMQ负责送达和控制时序.
否则,多段传输中的环节进程或网络故障都可能导致消息不能被送达。
但这种设计可靠但增加了负载和延迟.对于在线聊天消息,应该在客户端之间确认.
2.3UMX的一个缺陷
今天发现一个缺陷.
UMX支持参数为消息类型.
但是,消息不能是UMXT的消息.
这是因为UMXT没有重载CalcSize方法,导致序列化失败.
UMXT在Stream时才把传输属性加入消息包中.