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此情景完必.

|