myicq情景分析

类别:VC语言 点击:0 评论:0 推荐:

1.第一个情景:用户注册

1.第一个情景:用户注册
这个情景主要用到的数据结构:
(1)IcqUser
让我们先来看IcqUser的代码,
class IcqUser : public IcqInfo {
public:
IcqUser();

void load(DBInStream &in);
void save(DBOutStream &out);

string passwd;
uint8 auth;
};
class IcqInfo : public ContactInfo, public DBSerialize {
public:
IcqInfo();
virtual ~IcqInfo() {}

void save(DBOutStream &out);
void load(DBInStream &in);
};
class ContactInfo {
public:
QID qid;
string nick;
uint8 face;
uint8 gender;
uint8 age;
string country;
string province;
string city;

string email;
string address;
string zipcode;
string tel;

string name;
uint8 blood;
string college;
string profession;
string homepage;
string intro;

uint32 status;
};
class DBSerialize {
public:
virtual void save(DBOutStream &out) = 0;
virtual void load(DBInStream &in) = 0;
};
很明显可以看出它是用来存储人员基本信息的数据结构,它有两个方法save和load,我们来看看它们的代码,
可以想出它是用来将数据成员存入数据库和从数据库读出.DBSerialize是一个抽象类。
void IcqUser::load(DBInStream &in)
{
IcqInfo::load(in);

in >> passwd >> auth;
}

void IcqUser::save(DBOutStream &out)
{
IcqInfo::save(out);

out << passwd << auth;
}
void IcqInfo::load(DBInStream &in)
{
in >> face >> nick >> age >> gender >> country >> province >> city;
in >> email >> address >> zipcode >> tel;
in >> name >> blood >> college >> profession >> homepage >> intro;
}

void IcqInfo::save(DBOutStream &out)
{
out << face << nick << age << gender << country << province << city;
out << email << address << zipcode << tel;
out << name << blood << college << profession << homepage << intro;
}
/*
* Utility class that reads from or writes to a data block
*/
class DBOutStream {
public:
DBOutStream() {
cursor = data;
}
char *getData() {
return data;
}
int getSize() {
return (cursor - data);
}

DBOutStream &operator <<(uint8 b);
DBOutStream &operator <<(uint16 w);
DBOutStream &operator <<(uint32 dw);
DBOutStream &operator <<(const char *str);
DBOutStream &operator <<(StrList &strList);
DBOutStream &operator <<(const string &str) {
return operator <<(str.c_str());
}
DBOutStream &operator <<(const QID &qid) {
return (*this << qid.uin << qid.domain);
}

private:
char data[MAX_BLOCK_SIZE];
char *cursor;
};
class DBInStream {
public:
DBInStream(void *d, int n) {
cursor = data = (char *) d;
datalen = n;
}
DBInStream &operator >>(uint8 &b);
DBInStream &operator >>(uint16 &w);
DBInStream &operator >>(uint32 &dw);
DBInStream &operator >>(string &str);
DBInStream &operator >>(StrList &strList);
DBInStream &operator >>(QID &qid) {
return (*this >> qid.uin >> qid.domain);
}

private:
char *data;
char *cursor;
int datalen;
};

(2)IcqOption
class IcqOption : public DBSerialize {
public:
IcqOption();
virtual ~IcqOption();

void load(DBInStream &in);
void save(DBOutStream &out);
void playSound(int sound, IcqContact *c = NULL);

bitset<NR_USER_FLAGS> flags; // System option flags
uint32 hotKey;
string soundFiles[NR_SOUNDS];
string host;
uint16 port;
string autoReplyText;
string quickReplyText;
uint32 autoStatus;
uint32 autoStatusTime;
string skin;

// Proxy stuff
uint8 proxyType;
ProxyInfo proxy[NR_PROXY_TYPES];

private:
bool playSound(const char *file);
};
存储各种选项

在这个情景里,我们假定用户已经点击网络设置那一页的下一步按钮。进入以下代码中:
BOOL CRegFinishDlg::OnSetActive()
{
206 CRegWizard *wiz = (CRegWizard *) GetParent();
207 wiz->getData(&icqLink->myInfo, &icqLink->options);

首先206行取到上个窗口的指针,就是属性表的指针,进而调用它的函数getData。进入代码:
CRegFinishDlg::OnSetActive()-->CRegWizard::getData()

54 void CRegWizard::getData(IcqUser *info, IcqOption *options)
55 {
56 if (modeDlg.m_mode == 0)
57 info->qid = qid;
58 else {
59 info->qid.uin = modeDlg.m_uin;
60 info->qid.domain = finishDlg.domain;
61 }
62
63 info->face = basicDlg.m_pic;
64 info->age = basicDlg.m_age;
65 info->nick = basicDlg.m_nick;
66 info->gender = basicDlg.m_gender;
67 info->country = basicDlg.m_country;
68 info->province = basicDlg.m_province;
69 info->city = basicDlg.m_city;
70
71 info->email = commDlg.m_email;
72 info->address = commDlg.m_address;
73 info->zipcode = commDlg.m_zipcode;
74 info->tel = commDlg.m_tel;
75
76 info->name = miscDlg.m_name;
77 info->blood = miscDlg.m_blood;
78 info->college = miscDlg.m_college;
79 info->profession = miscDlg.m_profession;
80 info->homepage = miscDlg.m_homepage;
81 info->intro = miscDlg.m_intro;
82
83 info->passwd = (modeDlg.m_mode == 0 ? basicDlg.m_passwd : modeDlg.m_passwd);
84
85 if (options) {
86 options->host = networkDlg.m_host;
87 options->port = networkDlg.m_port;
88 options->flags.set(UF_USE_PROXY, networkDlg.m_useProxy);
89 options->proxyType = networkDlg.m_proxyType;
90 for (int i = 0; i < NR_PROXY_TYPES; ++i)
91 options->proxy[i] = networkDlg.proxyInfo[i];
92 }
93 }
此方法传入两个结构IcqUser和IcqOption以上有说明
第56行到第61行,主要给qid赋值,modeDlg.m_mode如选中新申请一个QQ号则为0,选中已有QQ号为1,本情景为0,也就是将info->qid = qid; qid是IcqWindow的一个公有变量QID qid

class QID {
public:
QID() {
uin = 0;
}
bool operator ==(const QID &qid) {
return (uin == qid.uin && !strcasecmp(domain.c_str(), qid.domain.c_str()));
}
bool isAdmin() {
return (uin == ADMIN_UIN && domain.empty());
}
char *toString();
bool parse(const char *qid);

uint32 uin;
string domain;
};
剩下的代码就是根据这几个对话框所添入的东东给全局变量icqLink中的IcqUser和IcqOption的各成员变量赋值.
回到RegFinishDlg::OnSetActive() 中第208行开始,
208 wiz->qid.uin = 0;
209
210 CString str;
211 str.LoadString(IDS_PLEASE_WAIT);
212 SetDlgItemText(IDC_STATUS, str);
213 str.LoadString(IDS_REG_REGISTERING);
214 SetDlgItemText(IDC_STATUS_DETAIL, str);
215 wiz->SetWizardButtons(PSWIZB_DISABLEDFINISH);
216
217 m_faceLabel.start();
218
219 resolveHost();
220
221 return CPropertyPage::OnSetActive();
}
这行将MyIcq号赋为0,211-218设置一些状态,219进入CRegFinishDlg::resolveHost()

void CRegFinishDlg::resolveHost()
{
172   CRegWizard *wiz = (CRegWizard *) GetParent();
173   const char *host = wiz->networkDlg.m_host;
174
175   if (wiz->networkDlg.m_useProxy && !wiz->networkDlg.m_proxyResolve)
176       getUdpSession()->connect(host, wiz->networkDlg.m_port);
177   else {
178       struct in_addr addr;
179       addr.s_addr = inet_addr(host); //将x.x.x.x转换为无符号整数给addr.s_addr
180       if (addr.s_addr != INADDR_NONE)
181           onHostFound(addr);
182       else
183           ((CIcqDlg *) AfxGetMainWnd())->getHostByName(host);
    }
}

175行看出在网络设置对话框中有没有选使用代理服务器,本情景中没选到178行将用户选定的地址作为参数传给onHostFound(addr);进入onHostFound

void CRegFinishDlg::onHostFound(struct in_addr &addr)
{
163   if (addr.s_addr != INADDR_NONE) {
164       CRegWizard *wiz = (CRegWizard *) GetParent();
165       getUdpSession()->connect(inet_ntoa(addr), wiz->networkDlg.m_port);
166   } else
167       onTimeout();
}

在165行调用函数getUpdSession()->connect传入参数是用户输入的地址和端口。

inline UdpSession *getUdpSession()
{
    return icqLink->udpSession;
}

void UdpSession::connect(const char *host, uint16 port)
{
090   IcqOption &options = icqLink->options;
091
092   // Connect using proxy
093   if (options.flags.test(UF_USE_PROXY)) {
094       if (options.proxyType == PROXY_SOCKS)
095           socksProxy.start(options.proxy[PROXY_SOCKS], sock);
096       else if (options.proxyType == PROXY_HTTP)
097               httpProxy.start(host, options.proxy[PROXY_HTTP]);   }
099   else{   //not use proxy

100       sockaddr_in addr;
101       socklen_t len = sizeof(addr);
103       memset(&addr, 0, sizeof(addr));
104       addr.sin_family = AF_INET;
105       addr.sin_port = htons(port);
106       addr.sin_addr.s_addr = inet_addr(host);
107       ::connect(sock, (sockaddr *) &addr, len);
109       getsockname(sock, (sockaddr *) &addr, &len);
110       realIP = ntohl(addr.sin_addr.s_addr);
112       icqLink->onConnect(true);

         }
}

首先判断有没有用proxy本情景中没有用所以到100行调用几个SOCKET函数连接传入的服务器及端口,将真实IP存放在realIP中,然后调用icqLink->onConnect(true);这个函数是一个虚函数,

CicqDlg类改写了它 ,所以程序进入CIcqDlg::onConnect

void CIcqDlg::onConnect(bool success)
{
292 IcqWindow *win = findWindow(WIN_REG_WIZARD);

进入findWindow

IcqWindow *IcqLink::findWindow(int type, QID *qid, uint32 seq)
{
    PtrList::iterator it;
    for (it = windowList.begin(); it != windowList.end(); ++it) {
        IcqWindow *win = (IcqWindow *) *it;
        if (win->type == type && (!qid || win->qid == *qid)

            && (seq == 0 || win->seq == seq))
          return win;
        }
    return NULL;
}

接上程序
293 if (win) {
294     ((CRegWizard *) win)->onConnect(success);
295     return;
296 }
........}

本情景中WIN_REG_WIZARD存在所以进入((CRegWizard *) win)->onConnect(success);

void onConnect(bool success) {
    finishDlg.onConnect(success);
}

void CRegFinishDlg::onConnect(bool success)
{
148    if (success) {
149        CRegWizard *wiz = (CRegWizard *) GetParent();
150        if (wiz->modeDlg.m_mode == 0)
151            wiz->seq = getUdpSession()->regNewUIN(wiz->basicDlg.m_passwd);
152        else {
153          icqLink->myInfo.qid.uin = wiz->modeDlg.m_uin;
          wiz->seq = getUdpSession()->login(wiz->modeDlg.m_passwd,STATUS_OFFLINE);
155        }
156   } else
157        onTimeout();
}

modeDlg.m_mode如选中新申请一个QQ号则为0,选中已有QQ号为1,本情景为0所以到151行

uint16 UdpSession::regNewUIN(const char *passwd)
{
 001   initSession();
 002   UdpOutPacket *out = createPacket(UDP_NEW_UIN);
 003   *out << passwd;
 004   return sendPacket(out);

先看001 regNewUIN主要功能是注册一个新ID,

void UdpSession::initSession()
{
161 sid = (rand() & 0x7fffffff) + 1;   //将sid初值赋值为随机数

162 sendSeq = (rand() & 0x3fff);     

163 retryKeepAlive = 0;
164 sessionCount = 0;
165
166 memset(window, 0, sizeof(window));
167
168 clearSendQueue();                        //清空发送队列
}

再看002

(1) UdpOutPacket *UdpSession::createPacket(uint16 cmd)
{
    UdpOutPacket *p = new UdpOutPacket;
    p->cmd = cmd;
    p->seq = ++sendSeq;
    createPacket(*p, cmd, sendSeq);
    return p;
}

(2) void UdpSession::createPacket(UdpOutPacket &out, uint16 cmd, uint16 seq)
{
    out << (uint16) MYICQ_UDP_VER << (uint32) 0;
    out << icqLink->myInfo.qid.uin << sid << cmd << seq;
    out << (uint16) 0; // Checkcode will be calculated later
}

(3) OutPacket &IcqOutPacket::operator <<(uint16 w)
{
    if (cursor <= data + MAX_PACKET_SIZE - sizeof(w)) {
        *(uint16 *) cursor = htons(w);
        cursor += sizeof(w); }
    return (*this);
}

继承关系:UdpOutPacket->IcqOutPacket->OutPacket->Packet

cmd = UDP_NEW_UIN,cusor和data的初始化是在哪里做的呢?看代码:

class IcqOutPacket : public OutPacket {
  public:
    IcqOutPacket() {
        cursor = data; }

  ...
  ...

  protected:
    char data[MAX_PACKET_SIZE];
    char *cursor;};

(3)是第一句主要是为了判断cursor的长度+sizeof(w)是否超过了MAX_PACKET_SIZE如没超过就将w转换为网络字节后赋值给cursor当前的地址

   然后移动cursor的指针+w

(2)中的后几句都是将数据赋给data.  也就是说将MYICQ_UDP_VER -0- icqLink->myInfo.qid.uin -sid - cmd -seq-0连起来赋给data.

   其中 MYICQ_UPD_VER = 1,icqLink->myInfo.qid.uin=申请一个QQ号则为0,选中已有QQ号为1,sid=随机数,cmd =UDP_NEW_UIN,seq=随机数

再看003: *out << passwd; 再给data加上passwd;

最后004: sendPacket(out)  看看

 

uint16 UdpSession::sendPacket(UdpOutPacket *p)
{
    // Packet must be encrypted before sending to server
1    if (p->cmd != UDP_NEW_UIN && p->cmd != UDP_LOGIN)
2        p->encrypt();
3    p->attempts = 0;
4    p->expire = time(NULL) + SEND_TIMEOUT;
5    sendDirect(p);
6    sendQueue.push_back(p);
7    return sendSeq;
}

1-2 如果cmd不等于注册新ID和登陆的话就加密.

expire是定义超时时间, attempts 企图尝试的次数,

到达5

void UdpSession::sendDirect(UdpOutPacket *out)
{
    if (icqLink->isProxyType(PROXY_HTTP))
        sendDirect(out, httpProxy.sock);
    else
        sendDirect(out, sock);
}

是否使用了代理服务器如没使用就用int sock,它在UpdSession的构造函数中被初始化为

sock = IcqSocket::createSocket(SOCK_DGRAM, this);

int IcqSocket::createSocket(int type, SocketListener *l)
{
    CMySocket *pSocket = new CMySocket(l);
    pSocket->Create(0, type, FD_READ | FD_CONNECT | FD_CLOSE);
    return addSocket(pSocket);
}

(1) createSocket第一行在堆上创建了一个CMySocket 它继承于CAsyncSocket类,看它的构造函数

    CMySocket::CMySocket(SocketListener *l)
        {   listener = l;}
    SocketListener是一个纯虚函数,它是UdpSession的父类,它改写了SocketListener的onReceive的虚函数。

(2) createSocket第二行是Create函数,以SOCK_DGRAM(TCP协议)创建了一个Socket对象

(3) 看看第三行的代码

    inline int addSocket(CAsyncSocket *pSocket)
    {
        int sock = *pSocket;
        sockHash.SetAt(sock, pSocket);
        return sock;
    }

    static CMap<int, int, CAsyncSocket*, CAsyncSocket*> sockHash;

 

void UdpSession::sendDirect(UdpOutPacket *out, int s)
{
203   char buf[MAX_PACKET_SIZE + 256];
204   char *p = buf;
205   int n = out->getSize();                               
206
207   IcqOption &options = icqLink->options;
208
209   if (options.flags.test(UF_USE_PROXY)) {
210       switch (options.proxyType) {
211         case PROXY_HTTP:
212             *(uint16 *) p = htons(n);
213             p += sizeof(uint16);
214             break;
215
216         case PROXY_SOCKS:
217             *(uint16 *) p = 0;
218             p += sizeof(uint16);
219             *p++ = 0;
220             if (options.proxy[PROXY_SOCKS].resolve) {
221                 // IPv4
222                 *p++ = 1;
223                 *(uint32 *) p = destIP;
224                 p += sizeof(uint32);
225             } else {
226                 // Domain name
227                 *p++ = 3;
228                 string &domain = icqLink->myInfo.qid.domain;
229                 uint8 len = domain.length();
230                 *p++ = len;
231                 memcpy(p, domain.c_str(), len);
232                 p += len;
233             }
234             *(uint16 *) p = htons(options.port);
235             p += sizeof(uint16);
236             break;
237         }
238   }
239   memcpy(p, out->getData(), n);
240   p += n;
241   send(s, buf, p - buf, 0);
  }

此函数功能主要是通过send()发送数据(updsesson::data)给服务器,本情景也就是发用户填写的信息.

从此将进入服务器代码直到服务器返回为此。看服务器代码:

此函数在main.cpp中

int main(int argc, char *argv[])
{
66     WSADATA wsaData;
67     WORD version = MAKEWORD(2, 2);
68     if (WSAStartup(version, &wsaData) != 0)
69         return 1;
70
71     initArgs(argc, argv);
72
73 #ifndef _DEBUG
74     if (!startService())
75 #endif
76     {
77      // We are not in service mode
78         if (myicqStart()) {
79             handlePacket();
80             myicqDestroy();
81         }
82     }
83
84     WSACleanup();
85     return 0;
}

66行到70行初始化winsock,版本为2.2。71行的initArgs(argc,argv)处理参数主要是如果是windows的话将myicq加入到服务中,以后系统启动时自动运行。到78行我们看myicqStart的代码(main.cpp),main()->myicqStart()

bool myicqStart()
{
45     if (!myicqInit())
46         return false;
进入myicqInit(myicq.cpp)中,main()->myicqStart()->myicqInit()

bool myicqInit()
{
187     srand(time(&curTime));
188
189     parseProfile(CONFIG_FILE, parseConfig);
190
191     Log::open(_ops.logFile.c_str(), _ops.logLevel);
192
193     loadPlugins();
194
195     // Initialize database subsystem
196     if (!DBManager::init(_ops.dbInfo)) {
197         LOG(1) ("Cannot connect to mysql master\n");
198         return false;
199     }
200     for (int i = 0; i < NR_DB_QUERY; ++i) {
201         if (!dbQuery[i].create(_ops.dbInfo)) {
202             LOG(1) ("Cannot connect to mysql slave\n");
203             return false;
204         }
205     }
206
207     if (!UdpSession::init())
208         return false;
209
210     if (_ops.enableS2S) {
211         if (!Server::init())
212             return false;
213
214         LOG(1) ("domain name is %s\n", _ops.domain.c_str());
215     }
216
217     LOG(1) ("myicqd is now started.\n");
218     return true;
}

189行的parseProfile是函数主要功能是从CONFIG_FILE指定的文件中取各各节的内容存入OPTIONS _ops这个全局结构中.

struct OPTIONS {
    // network
    uint32 myicqIP;
    uint16 myicqPort;
    // log
    int logLevel;
    string logFile;
    // mysql
    DB_INFO dbInfo;
    int enableRegister;
    int enableS2S;
    // server
    string domain;
    string desc;
    // plugins
    string pluginDir;
};

191打开日志文件,193加载插件模块。196-199通过调用mysql_init(mysql提供)并用mysql_real_connect连接;初始化mysql赋给mysqlWrite,参数是_ops.dbInfo.,200-205也是调用mysql_init来初始化mysql_real_connect连接;static DBManager dbQuery[NR_DB_QUERY];结构中的MYICQ* mysqlRead;,207-208初始化UPD服务UpdSession::init()(updsession.cpp)

main()->myicqStart()->myicqInit()->UpdSession::init()

bool UdpSession::init()
{
696     sockaddr_in addr;
697
698     qidAdmin.uin = ADMIN_UIN;
699     qidAdmin.domain = emptyStr;
700
701     sock = socket(AF_INET, SOCK_DGRAM, 0);
702     if (sock < 0) {
703         LOG(1) ("socket() failed\n");
704         goto failed;
705     }
706
707     memset(&addr, 0, sizeof(addr));
708     addr.sin_family = AF_INET;
709     addr.sin_addr.s_addr = _ops.myicqIP;
710     addr.sin_port = _ops.myicqPort;
711     if (bind(sock, (sockaddr *) &addr, sizeof(addr)) < 0) {
712         LOG(1) ("bind(): Can not bind on %s:%d\n",
713             inet_ntoa(addr.sin_addr), ntohs(_ops.myicqPort));
714         goto failed;
715     }
716
717     desinit(0);
718     return true;
719
720     failed:
721     if (sock >= 0)
722         close(sock);
723     return false;
}

此函数就是对socket的一些初始化工作socket初始化流程 socket() ---  bind() --- listen()注:此函数中还没有listen()用的是(无连接的)UDP协议

Server::init()主要是初始化(面向连接的)TCP协议,然后返回到myicqStart中看代码:

48     DWORD id;
49     int i;
50
51     // Creating threads...
52     CreateThread(NULL, 0, pulseThread, NULL, 0, &id);
53
54     // DNS
55     CreateThread(NULL, 0, dnsThread, NULL, 0, &id);
56
57     CreateThread(NULL, 0, dbUpdateThread, NULL, 0, &id);
58     for (i = 0; i < NR_DB_QUERY; ++i)
59         CreateThread(NULL, 0, dbQueryThread, (LPVOID) i, 0, &id);
60
61     return true;
}
建立了几个线程回调函数分别是pulseThread, dnsThread, dbUpdateThread, dbQueryThread;

static DWORD WINAPI pulseThread(LPVOID)
{
    pulse();
    return 0;
}

void pulse()
{
    for (;;) {
        time((time_t *) &curTime);
        sleep(1);
    }
}

static DWORD WINAPI dnsThread(LPVOID)
{   
    handleDNS();
    return 0;
}

void handleDNS()
{
    DNSManager::process();
}

static DWORD WINAPI dbUpdateThread(LPVOID)
{
    handleDBUpdate();
    return 0;
}

void handleDBUpdate()
{
    DBManager::processUpdate();
}

先继续向下看吧!回到main中还剩以下几行

79             handlePacket();
80             myicqDestroy();
81         }
82     }
83
84     WSACleanup();
85     return 0;

}

最重要的是handlePacket(),看代码:

void handlePacket()
{
240     int sock = UdpSession::sock;
241
242     for (;;) {
243         fd_set readfds;
244         fd_set writefds;
245
246         FD_ZERO(&readfds);
247         FD_ZERO(&writefds);
248
249         FD_SET(sock, &readfds);
250         int maxfd = sock;
251
252         if (_ops.enableS2S) {
253             int n = Server::generateFds(readfds, writefds);
254             if (n > maxfd)
255                 maxfd = n;
256         }
257
258         timeval to;
259         to.tv_sec = 1;
260         to.tv_usec = 0;
261
262         int n = select(maxfd + 1, &readfds, &writefds, NULL, &to);
263
264         if (n > 0) {
265             if (FD_ISSET(sock, &readfds))
266                 UdpSession::onReceive();
267
 

此函数用了winsock中的select模型,有to所以为非阻塞状态如发现有未读入的数据就调用UdpSession::onReceive()来处理,我们继续,

handlePacket()->UdpSession::onReceive(

bool UdpSession::onReceive()
{
852     char buf[UDP_PACKET_SIZE];
853     sockaddr_in addr;
854     socklen_t len = sizeof(addr);
855     int n = recvfrom(sock, buf, UDP_PACKET_SIZE, 0, (sockaddr *) &addr, &len);
856
857     if (n < (int) sizeof(UDP_CLI_HDR))
858         return false;
859
860     UdpInPacket in(buf, n);
861
 

855-858从指定的sock中接收到字符后,建立一个UdpInPacket对象,它的继承关系是UdpInPacket->IcqInPacket->InPacket,构造函数如下

IcqInPacket(char *d, int n)
{
    cursor = data = (uint8 *) d;
    datalen = n;
}

 

UdpInPacket::UdpInPacket(char *d, int n):IcqInPacket(d, n)
{
    *this >> header.ver >> header.reserved;
    *this >> header.uin >> header.sid >> header.cmd;
    *this >> header.seq >> header.cc;
}

UDP_CLI_HDR header;

struct UDP_CLI_HDR {
    uint16 ver;
    uint32 reserved;
    uint32 uin;
    uint32 sid;
    uint16 cmd;
    uint16 seq;
    uint16 cc; // check code
};

InPacket &IcqInPacket::operator >>(uint8 &b)
{
    b = 0;
    if (cursor <= data + datalen - sizeof(b))
    b = *cursor++;
    return *this;
}
InPacket &IcqInPacket::operator >>(uint16 &w)
{
    w = ntohs(read16());
    return *this;
}
InPacket &IcqInPacket::operator >>(uint32 &dw)
{
    dw = ntohl(read32());
    return *this;
}
InPacket &IcqInPacket::operator >>(ICQ_STR &str)
{
    uint16 len;
    operator >>(len);

    if (len && cursor <= data + datalen - len && !cursor[len - 1]) {
        str.text = (char *) cursor;
        str.len = len - 1;
        cursor += len;
        } else {
        str.text = "";
        str.len = 0;
    }
    return *this;
}

uint16 IcqInPacket::read16()
{
    uint16 w = 0;
    if (cursor <= data + datalen - sizeof(w)) {
    w = *(uint16 *) cursor;
    cursor += sizeof(w);
    }
    return w;
}
uint32 IcqInPacket::read32()
{
    uint32 dw = 0;
    if (cursor <= data + datalen - sizeof(dw)) {
        dw = *(uint32 *) cursor;
    cursor += sizeof(dw);
    }
    return dw;
}

上面函数的大概意思就是通过运算符重载把读入的数据从网络字节转换为主机字节后赋给UdpInPacket类的公有成员变量header;

让我们继续UdpSession::onReceive()

862     uint32 ip = addr.sin_addr.s_addr;
863     uint16 port = addr.sin_port;
864     UdpSession *s = SessionHash::get(ip, port);
865
866     if (!s) {
867         uint16 cmd = in.header.cmd;
868         if (cmd == UDP_NEW_UIN || cmd == UDP_LOGIN) {
869             s = new UdpSession(in, ip, port);
870
871             // Add it to the hash
872             SessionHash::put(s, ip, port);
873             keepAliveList.add(&s->listItem);
874             } else
875         s = SessionHash::get(in.header.uin);
876     }
877     if (s)
878         s->onPacketReceived(in);
879
880     return true;
}

862和863把客户端的ip和port取出来,看864根据UdpSession的成员变量的队列ipportItem中找出ip、port相同的然后返回一个UdpSession

866-876如果队列中没有的话,并且是新建Id或是登录就新创建一个UdpSession然后用ipportItem将它链入bucket队列中,然后用listItem

链入keepAliveList队列。再调用onPacketReceived,这个函数是一个swith和case用来处理各种命令的跳转函数对于UPD_NEW_UIN的话就是onNewUIN(in);看onNewUIN

handlePacket()->UdpSession::onReceive()->UdpSession::onNewUIN

void UdpSession::onNewUIN(UdpInPacket &in)
{
1314    if (_ops.enableRegister) {
1315        ICQ_STR passwd;
1316        in >> passwd;

1317        DBRequest *req = new DBRequest(DBF_UPDATE | DBF_INSERT, newUserCB, this, in.header.seq);
1318        WRITE_STR(req, "INSERT INTO basic_tbl (passwd, msg_id) SELECT PASSWORD(");
1319        *req << passwd;
1320        WRITE_STR(req, "), MAX(id) FROM bcmsg_tbl");
1321        DBManager::query(req);
1322    } else {
1323        UdpOutPacket *out = createPacket(UDP_NEW_UIN, in.header.seq);
1324        *out << (uint32) 0;
1325        sendPacket(out);
1326    }
1327    isDead = 1;
}

如果现在启用了注册的话就将in中的密码赋给passwd,1317行新建一个DBRequest对象

DBRequest::DBRequest(uint8 f, DB_CALLBACK cb, RefObject *obj, uint32 d)
{
    flags = f;
    callback = cb;
    refObj = obj;
    data = d;
    res = NULL;
    cursor = sql;
}

注意:将sql的地址赋给了cursor.1318如下:

#define WRITE_STR(req, str) req->writeString(str, sizeof(str) - 1)

void writeString(const char *text, int n)

{
    if (cursor - sql + n <= MAX_SQL)

    {
        memcpy(cursor, text, n);
        cursor += n;
    }
}

1318-1320构成的SQL语句如下:

INSERT INTO basic_tbl (passwd, msg_id) SELECT PASSWORD("你的密码"),MAX(id) FROM bcmsg_tbl;

然后到1321:

handlePacket()->UdpSession::onReceive()->UdpSession::onNewUIN->DBManager::query

void DBManager::query(DBRequest *req)
{
    if (req->callback && req->refObj)
        req->refObj->addRef();
    Queue &q = ((req->flags & DBF_UPDATE) ? updateQueue : queryQueue);
    q.put(&req->listItem);
}

根据SQL语句的类型(UPDATE,INSERT...)判断是用updateQueue还是queryQueue队列,本情景用queryQueue,将这个UdpSession的listItem放入

queryQueue队列中,等待处理线程dbQueryThread处理,然后返回到handlePacket()中第268行

268             if (_ops.enableS2S)
269                 Server::examineFds(readfds, writefds);
270
271         } else if (n < 0) {
272             if (errno == EINTR)
273                 continue;
274
275             LOG(1) ("select() failed\n");
276             break;
277         }
278
279         DBManager::dispatch();
280         DNSManager::dispatch();
281
282         static time_t lastTime;
283         if (curTime != lastTime) {
284             lastTime = curTime;
285             checkTimeOuts();
286         }
287     }
}
168-270用于TCP/IP连先略过,到了279

handlePacket()->DBManager::dispatch()

void DBManager::dispatch()
{
    while (!resultQueue.isEmpty()) {
        ListHead *pos = resultQueue.get();
        DBRequest *req = LIST_ENTRY(pos, DBRequest, listItem);
        RefObject *obj = req->refObj;
        MYSQL_RES *res = req->res;

        req->callback(req);
        if (obj)
            obj->release();

        if (!(req->flags & DBF_UPDATE) && res)
            mysql_free_result(res);
        delete req;
    }
}

dbQueryThread线程处理后会放入到resultQueue中,callback是一个函数指针在结构刚刚建立时赋的为newUserCB

static void newUserCB(DBRequest *req)
{
    UdpSession *session = (UdpSession *) req->refObj;
    UdpOutPacket *out = session->createPacket(UDP_NEW_UIN, req->data);
   

    uint32 uin = 0;
    if (req->ret == 0) {
        uin = req->lastInsertID;
        DBRequest *req = new DBRequest(DBF_UPDATE);
        WRITE_STR(req, "INSERT INTO ext_tbl (uin) VALUES(");
        *req << uin << ')';
        DBManager::query(req);
    }
    session->uin = uin;
   

    LOG(2) ("%lu registered\n", uin);
   

    *out << uin;
    *out << _ops.domain.c_str();
    session->sendPacket(out);
}
可以看出是给客户端发送一个数据包,此sendPacket和客户端那个还有点不一样

void UdpSession::sendPacket(UdpOutPacket *p)
{
     p->attempts = 0;
     p->expire = curTime + SEND_TIMEOUT;

     sendDirect(p);
1    sendQueue.add(&p->sendItem);
2    globalSendQueue.add(&p->globalSendItem);
}

inline void UdpSession::sendDirect(UdpOutPacket *p)
{
    p->send(sock, ip, port);
}

int UdpOutPacket::send(int sock, uint32 ip, uint16 port)
{
    sockaddr_in addr;
    // Is it neccesary?
    //memset(&addr, 0, sizeof(addr));

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = ip;
    addr.sin_port = port;
    return sendto(sock, (char *) data, cursor - data, 0, (sockaddr *) &addr, sizeof(addr));
}

这几个函数调用是向sendto向客户端发送回信息。1和2放入功能是将sendItem,globalSendItem链入sendQueue和globalSendQueue队列。

 

 

前面说过程序刚启动时创建了三个线程其中dbQueryThread就是用来处理queryQueue对列的看代码:

static DWORD WINAPI dbQueryThread(LPVOID i)
{
    handleDBQuery((int) i);
    return 0;
}

void handleDBQuery(int i)
{
    dbQuery[i].processQuery();
}

static DBManager dbQuery[NR_DB_QUERY];

void DBManager::processQuery()
{
    for (;;) {
        DBRequest *req = getDBRequest(queryQueue);
        if (mysql_real_query(mysqlRead, req->sql, req->sqlLen()) == 0)
            req->res = mysql_store_result(mysqlRead);
        putDBRequest(req);
    }
}

inline DBRequest *getDBRequest(Queue &q)
{
    ListHead *pos = q.get();
    return LIST_ENTRY(pos, DBRequest, listItem);
}

mysql_real_query是mysql提供的API函数,功能是执行sql语句返回0时表示成功非0表示矢败,

mysql_store_result是mysql提供的API函数,功能返回结果集,然后看putDBRequest(req);

inline void putDBRequest(DBRequest *req)
{
    if (req->callback)
        resultQueue.put(&req->listItem);
    else
        delete req;
}

放入resultQueue队列中。



这样服务器端又发送数据给客户端,明天再看吧!(2004.03.17)

2004.0.23 不会吧!又有5天没有看了要加紧呀!回到客户端

uint16 UdpSession::sendPacket(UdpOutPacket *p)
{
    // Packet must be encrypted before sending to server
1    if (p->cmd != UDP_NEW_UIN && p->cmd != UDP_LOGIN)
2        p->encrypt();
3    p->attempts = 0;
4    p->expire = time(NULL) + SEND_TIMEOUT;
5    sendDirect(p);
6    sendQueue.push_back(p);
7    return sendSeq;
}

在执行完了第5行后,再看第6行:PtrList sendQueue; 在icqtypes.h中定义了typedef list<void *> PtrList;

list是一个标准模版库的类. push_back是将p放入sendQueue的队列中:然后返回,赋值给了?CRegWizard *wiz的seq成员变量。

客户端代码曾经在此创建了一个CMySocket对象,就是用它来发送给服务器端的,服务端又向这个SOCKET发送了一个数据包,那么它的

OnReceive函数将被调用,看它的代码

void CMySocket::OnReceive(int nErrorCode)
{

    listener->onReceive();

}

SocketListener *listener; 因为SocketListener是个纯虚类,在前面就看到过UpdSession的构造函数,

sock = IcqSocket::createSocket(SOCK_DGRAM, this);所以调用的是UpdSession::onReceive()

bool UdpSession::onReceive()
{
    char data[MAX_PACKET_SIZE];
    char *p = data;
    sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);

    // Receive data from udp socket
    int n = recvfrom(sock, data, sizeof(data), 0, (sockaddr *) &addr, &addrlen);
    if (n < 0)
        return false;

    if (icqLink->isProxyType(PROXY_SOCKS)) {
        if (data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 1)
            return false;

        p += 10;
        n -= 10;
    }

    UdpInPacket in(p, n);
    return onPacketReceived(in);
}

2004.03.25

从网络缓冲区读入数据后就创建了一个UdpInPacket in(p, n);对象

UdpInPacket::UdpInPacket(const char *d, int len) : IcqInPacket(d, len)
{
    *this >> header.ver >> header.reserved;
    *this >> header.uin >> header.sid >> header.cmd;
    *this >> header.seq >> header.ackseq;
}

这个函数和服务端的差不多主要是将接收到的数据存入header中,onPacketReceived(in)进行了一些判断后如果是新建ID就调用onNewUINReply(),让我们看代码

void UdpSession::onNewUINReply(UdpInPacket &in)
{
    QID qid;
    in >> qid.uin >> qid.domain;
    icqLink->onNewUINReply(qid);
}

onNewUINReply是个纯虚函数,前面已经看到过是CicqDlg是icqLink的子类,所以就调用了

void CIcqDlg::onNewUINReply(QID &qid)
{
    myInfo.qid = qid;
    IcqWindow *win = findWindow(WIN_REG_WIZARD);
    if (win)
        ((CRegWizard *) win)->onNewUINReply(qid);
}

又调用了它

void onNewUINReply(QID &qid) {
    finishDlg.onNewUINReply(qid);
}

又调了

void CRegFinishDlg::onNewUINReply(QID &qid)
{
    CRegWizard *wiz = (CRegWizard *) GetParent();
    wiz->qid = qid;

    CString str;

    if (qid.uin) {
001        wiz->isFinished = TRUE;
           str.LoadString(IDS_FINISHED);
           SetDlgItemText(IDC_STATUS, str);
           str.Format(IDS_REG_SUCCESS, qid.toString());
           SetDlgItemText(IDC_STATUS_DETAIL, str);
           wiz->SetWizardButtons(PSWIZB_FINISH);
007        wiz->GetDlgItem(IDCANCEL)->EnableWindow(FALSE);
    } else {
        str.LoadString(IDS_FAILED);
        SetDlgItemText(IDC_STATUS, str);
        str.LoadString(IDS_REG_FAILED);
        SetDlgItemText(IDC_STATUS_DETAIL, str);
    }
    m_faceLabel.stop();
}

终于又进入了MFC的领地,处理一些UI的东东,001-007设置了让闪的不闪,设置几个字符串后,将完成按钮启用. 然后一层一层返回然后调用

updSession::sendAckPacket(seq);给服务器一个应答。然后客户端就停下来等用户按完成按钮.用户按完成按钮后触发了CPropertyPage的OnOK( )此对象也没用改写所以一层一层返回到主窗口,OK此情景完必.

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 


 

 

 

 

 

 

 

 

 

本文地址:http://com.8s8s.com/it/it986.htm