myicq-1.0a1服务器代码分析(一)
顾剑辉(Solarsoft)
myicq代码的公布已经有一段时间了,听说作者张勇已经不再公开的服务器端的代码了,不尤觉得可惜,拜读他的作品已经有一段时间了,今天来发表一下自己的意见。
我现在就从整体的构架来谈谈他服务器端的代码,服务器代码可分成数据库、upd服务、服务器群组、插件四块。
我这里对upd服务的实现进行一些讨论,
一、数据缓冲包的类管理
先定义了抽象类OutPacket与InPacket,代码如下:
class OutPacket {
public:
virtual OutPacket &operator <<(uint8 b) = 0;
virtual OutPacket &operator <<(uint16 w) = 0;
virtual OutPacket &operator <<(uint32 dw) = 0;
virtual OutPacket &operator <<(ICQ_STR &str) = 0;
};
class InPacket {
public:
virtual InPacket &operator >>(uint8 &b) = 0;
virtual InPacket &operator >>(uint16 &w) = 0;
virtual InPacket &operator >>(uint32 &dw) = 0;
virtual InPacket &operator >>(ICQ_STR &str) = 0;
};
在实现其派生类IcqOutPacket与IcqInPacket,并定义数据缓冲区uint8 data[UDP_PACKET_SIZE],大小为1024,主要是处理数据的输入与输出。
再派生其类UdpOutPacket与UdpInPacket,此时进行了DES的数据加密与解密的处理,并加入数据头,定义如下:
struct UDP_CLI_HDR {
uint16 ver;
uint32 reserved;
uint32 uin;
uint32 sid;
uint16 cmd;
uint16 seq;
uint16 cc; // check code
};
struct UDP_SRV_HDR {
uint16 ver;
uint32 reserved;
uint32 uin;
uint32 sid;
uint16 cmd;
uint16 seq;
uint16 ackseq;
};
此时缓冲区的包类已经结束。
二、socket服务代码的编写
先定义2个类RefObject与 Session,其中RefObject为记数类,两代码如下:
class RefObject {
public:
RefObject() {
refCount = 1;
}
int addRef() {
return ++refCount;
}
int release() {
register int ret = --refCount;
if (!ret)
delete this;
return ret;
}
protected:
virtual ~RefObject() {}
int refCount;
};
class Session {
public:
uint32 uin;
uint32 status;
uint32 ip;
uint16 msgport;
uint32 realip;
};
现在我们来看一下udp服务类的定义
class UdpSession : public RefObject, public Session {
public:
UdpSession(UdpInPacket &in, uint32 ip, uint16 port);
~UdpSession();
UdpOutPacket *createPacket(uint16 cmd, uint16 ackseq = 0);
void sendPacket(UdpOutPacket *p);
void sendOnline(Session *s, ICQ_STR &domain);
void sendOffline(uint32 uin, ICQ_STR &domain);
void sendStatusChanged(Session *s, ICQ_STR &domain);
void sendMessage(uint8 type, QID &src, uint32 when, ICQ_STR &text);
void sendGroupTypes();
void updateContactReply(uint16 seq, uint8 *data, int n, Server *server = NULL);
void searchRandomReply(uint16 seq, uint8 *data, int n);
void searchUINReply(uint16 seq, uint8 *data, int n);
void dead();
void logout();
void onlineNotify();
void offlineNotify();
void statusNotify(uint32 newStatus);
static bool init();
static void destroy();
static bool onReceive();
static void checkSendQueue();
static void checkKeepAlive();
static void sendMessage(uint8 type, QID &dst, QID &src, uint32 when, ICQ_STR &text);
static void addFriend(uint16 seq, QID &dst, uint32 src, Server *server = NULL);
static void addFriendAuth(uint16 seq, QID &dst, uint32 src, Server *server, uint8 auth, bool sendAuthMsg = true);
static void updateContact(uint16 seq, uint32 dst, uint32 src, Server *server = NULL);
static void searchUIN(uint16 seq, uint32 dst, uint32 src, Server *server = NULL);
uint8 auth;
uint8 face;
char nickname[MAX_NICK + 1];
char province[MAX_PROVINCE + 1];
uint16 tcpver;
uint16 port;
uint32 oldMsgID, lastMsgID;
char subkey[128];
ListHead uinItem;
ListHead ipportItem;
ListHead listItem;
static int sock;
static uint32 sessionCount;
private:
void createPacket(UdpOutPacket &out, uint16 cmd, uint16 seq, uint16 ackseq);
void sendDirect(UdpOutPacket *p);
void sendAckPacket(uint16 seq);
bool setWindow(uint16 seq);
void notify(DB_CALLBACK cb1, DB_CALLBACK cb2);
void onAck(uint16 seq);
void onKeepAlive(UdpInPacket &in);
void onNewUIN(UdpInPacket &in);
void onGetContactList(UdpInPacket &in);
void onGetRemoteContactList(UdpInPacket &in);
void onLogin(UdpInPacket &in);
void onLogout(UdpInPacket &in);
void onChangeStatus(UdpInPacket &in);
void onUpdateContact(UdpInPacket &in);
void onModifyUser(UdpInPacket &in);
void onUpdateUser(UdpInPacket &in);
void onSendMessage(UdpInPacket &in);
void onSearchRandom(UdpInPacket &in);
void onSearchCustom(UdpInPacket &in);
void onAddFriend(UdpInPacket &in);
void onDelFriend(UdpInPacket &in);
void onSendBCMsg(UdpInPacket &in);
void onGetServerList(UdpInPacket &in);
void onGetGroupList(UdpInPacket &in);
void onSearchGroup(UdpInPacket &in);
void onCreateGroup(UdpInPacket &in);
void onEnterGroup(UdpInPacket &in);
void onExitGroup(UdpInPacket &in);
void onGroupStart(UdpInPacket &in);
void onGroupMessage(UdpInPacket &in);
void onGroupCmd(UdpInPacket &in);
bool onPacketReceived(UdpInPacket &in);
uint16 udpver;
uint32 sid;
uint16 sendSeq;
uint16 recvSeq;
uint32 window;
time_t expire;
uint8 isDead : 1;
IcqGroup *group;
ListHead sendQueue;
DECLARE_SLAB(UdpSession)
};
都是一些与客户端通信的接口。
我的评论与建议:这样的数据缓冲区的设计,代码执行效率不是很高,而且可能造成服务器缓冲区溢出的错误,应该设计时加入一些防范措施。
对UdpSession的设计不太合理,模块化不高,代码零乱。我到认为这里到可以用插件机制,也就是一个插件来管理部分的通信消息命令。这样可提高程序的可读性,还便于今后通信消息命令的扩展。这点可以学学入侵检测系统Snort的规则处理模块与插件的管理。