转载请注明出处: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抓取的使用飞鸽传书发送消息的一个数据包。