飛鴿傳書源碼分析四-訊息發送,飛鴿傳書源碼
轉載請註明出處:http://blog.csdn.net/mxway/article/details/44569831
本篇文章是在飛鴿傳書源碼v2.06的基礎上進行分析的
飛鴿傳書是一款工作在區域網路的軟體,支援區域網路裡不同裝置之間的訊息發送及檔案的傳輸(訊息發送使用udp,檔案傳輸使用tcp)。
發送訊息及檔案傳輸是在飛鴿傳書的發送對話方塊中進行,而發送對話方塊的開啟是通過雙擊拖盤(win7系統)到工作列的表徵圖。而拖盤到工作列的這個表徵圖就是飛鴿傳書的主視窗,對應的源碼就是Mainwin.cpp中的TMainWin類。下面是TMainWin類處理雙擊事件,滑鼠雙擊事件為什麼是由EventButton進行處理可以參見第二篇文章-訊息機制
BOOL TMainWin::EventButton(UINT uMsg, int nHitTest, POINTS pos){ switch (uMsg) { ... case WM_LBUTTONDBLCLK: case WM_NCLBUTTONDBLCLK: if (cfg->OneClickPopup == FALSE) SendDlgOpen(); return TRUE; ... }}BOOL TMainWin::SendDlgOpen(HWND hRecvWnd, MsgBuf *msg){ TSendDlg *sendDlg; ... if ((sendDlg = new TSendDlg(msgMng, shareMng, &hosts, cfg, logmng, hRecvWnd, msg)) == NULL) return FALSE; sendList.AddObj(sendDlg); sendDlg->Create(), sendDlg->Show(); ...}BOOL TDlg::Create(HINSTANCE hInstance){ TApp::AddWin(this); if ((hWnd = ::CreateDialog(hInstance ? hInstance : TApp::hI, resId ? (LPCSTR)resId : resName, parent ? parent->hWnd : NULL, (DLGPROC)TApp::WinProc)) == NULL) return TApp::DelWin(this), FALSE; else return TRUE;}
發送訊息的對話方塊類似如下
在發送對話方塊中在編輯框中輸入要發送的內容,在使用者列表中選擇要發送的對象,點send按鈕就可以將訊息發送到對方。這裡可以選擇對發送的訊息是否進行加密,為了簡化問題這裡只分析最簡單的訊息發送。
一、飛鴿傳書訊息發送格式
飛鴿傳書版本:資料包唯一編號:使用者名稱:機器名稱:命令:真實訊息
(1)飛鴿傳書版本是一個宏定義
#define IPMSG_VERSION 0x0001
(2)資料包唯一編號由飛鴿傳書程式自動產生的。
(3)使用者名稱:如果使用者沒有使用飛鴿傳書設定自己的名稱,預設使用的是當前登入系統的使用者名稱
(4)機器名稱,裝置的名稱如PC設定的電腦名稱
(5)命令,命令是一個32位的無符號數,其功能分為兩部分,後8位用於必選的命令,表示當對方收到該命令後要什麼事,如使用者上線通知,使用者退出通知,發送訊息。前面24位用於可選命令,如對傳送訊息進行加密,擷取加密的密鑰等。
(6)真實訊息,這個是真正要發給對方的資訊,包括要發送對方的訊息及要傳送給對方的檔案資訊。
產生上述格式訊息的代碼如下
ULONG MsgMng::MakeMsg(char *buf, int _packetNo, ULONG command, const char *msg, const char *exMsg, int *packet_len){ int len, ex_len = exMsg ? strlen(exMsg) + 1 : 0, max_len = MAX_UDPBUF; if (packet_len == NULL) packet_len = &len; //版本:資料包編號:使用者名稱:裝置名稱:命令: *packet_len = wsprintf(buf, "%d:%ld:%s:%s:%ld:", IPMSG_VERSION, _packetNo, local.userName, local.hostName, command); if (ex_len + *packet_len + 1 >= MAX_UDPBUF) ex_len = 0; max_len -= ex_len; if (msg != NULL) //LocalNewLineToUnix把\r\n轉換為\n *packet_len += LocalNewLineToUnix(msg, buf + *packet_len, max_len - *packet_len); (*packet_len)++; if (ex_len) { //如果有附加訊息(如檔案發送,同時發送附加的訊息) memcpy(buf + *packet_len, exMsg, ex_len); *packet_len += ex_len; } return _packetNo;}
二、訊息發送
單擊send按鈕的實現代碼如下:
BOOL TSendDlg::EvCommand(WORD wNotifyCode, WORD wID, LPARAM hWndCtl){ switch (wID) { case IDOK: ... SendMsg(); ... }}BOOL TSendDlg::SendMsg(void){ command = IPMSG_SENDMSG|IPMSG_SENDCHECKOPT; //擷取選中數 if ((sendEntryNum = (int)SendDlgItemMessage(HOST_LIST, LVM_GETSELECTEDCOUNT, 0, 0)) <= 0 || (sendEntry = new SendEntry [sendEntryNum]) == NULL) return FALSE; //擷取要發送的訊息資料 GetDlgItemText(SEND_EDIT, msg.msgBuf, MAX_UDPBUF); int storeCnt = 0, status = 0, cnt; int localStatus = sendEntryNum <= cfg->EncryptNum && (cfg->pubKey.Key() || cfg->smallPubKey.Key()) ? IPMSG_ENCRYPTOPT : 0; //擷取選中的host資訊,host資訊是由TMainWin傳過來的 for (cnt=0; cnt < memberCnt && storeCnt < sendEntryNum; cnt++) { if ((SendDlgItemMessage(HOST_LIST, LVM_GETITEMSTATE, cnt, LVIS_SELECTED) & LVIS_SELECTED) == 0) continue; char hostStr[MAX_LISTBUF]; Host *host = hostArray[cnt]; SendEntry *entry = &sendEntry[storeCnt++]; //發送的訊息要進行加密 status |= host->hostStatus & IPMSG_ENCRYPTOPT; MakeListString(cfg, host, hostStr); logmng->WriteSendHead(hostStr); entry->SetHost(host); entry->SetStatus((localStatus & host->hostStatus) ? host->pubKey.Key() == NULL ? ST_GETCRYPT : ST_MAKECRYPTMSG : ST_MAKEMSG); entry->SetCommand(command | (entry->Status() == ST_MAKEMSG ? 0 : IPMSG_ENCRYPTOPT)); } //發送的訊息太長進行截斷 msg.msgBuf[MAX_CRYPTLEN] = 0; if (status &= localStatus) command |= IPMSG_ENCRYPTOPT; logmng->WriteSendMsg(msg.msgBuf, command, shareInfo); if (shareInfo && shareInfo->fileCnt) // ...\0no:fname:size:mtime: { //如果選中的檔案或檔案夾進行傳輸,產生要傳輸的檔案資訊 char buf[MAX_UDPBUF / 2]; EncodeShareMsg(shareInfo, buf, sizeof(buf)); shareStr = new char [strlen(buf) + 1]; strcpy(shareStr, buf); shareMng->AddHostShare(shareInfo, sendEntry, sendEntryNum); } //真正進行發送訊息的函數 SendMsgSub(); return TRUE;}BOOL TSendDlg::SendMsgSub(void){ BOOL makeNomalMsg = TRUE; for (int cnt=0; cnt < sendEntryNum; cnt++) { //如果需要從要發送的客戶那擷取加密的密鑰,先擷取密鑰 if (sendEntry[cnt].Status() == ST_GETCRYPT) { char spec_str[MAX_BUF]; int spec = IPMSG_RSA_512 | IPMSG_RC2_40; if (cfg->pubKey.Key()) spec |= IPMSG_RSA_1024 | IPMSG_BLOWFISH_128; wsprintf(spec_str, "%x", spec); msgMng->Send(&sendEntry[cnt].Host()->hostSub, IPMSG_GETPUBKEY, spec_str); } //對要發送的資料進行加密 if (sendEntry[cnt].Status() == ST_MAKECRYPTMSG) { MakeEncryptPacket(sendEntry + cnt); // ST_MAKECRYPTMSG -> ST_SENDMSG } if (sendEntry[cnt].Status() == ST_MAKEMSG) { sendEntry[cnt].SetStatus(ST_SENDMSG); if (makeNomalMsg) msgMng->MakeMsg(msgBuf, packetNo, command & ~IPMSG_ENCRYPTOPT, msg.msgBuf, shareStr, &packetLen), makeNomalMsg = FALSE; } //在MakeEncryptPacket對訊息加密完成後設定host狀態為ST_SENDMSG if (sendEntry[cnt].Status() == ST_SENDMSG) { const char *str = sendEntry[cnt].Msg() ? sendEntry[cnt].Msg() : msgBuf; int len = sendEntry[cnt].Msg() ? sendEntry[cnt].MsgLen() : packetLen; //向選中的機器發送資料 msgMng->UdpSend(sendEntry[cnt].Host()->hostSub.addr, sendEntry[cnt].Host()->hostSub.portNo, str, len); } } return TRUE;}
下面是使用wireshark抓取的使用飛鴿傳書發送訊息的一個資料包。