//局域网内的聊天软件,显示局域网内在线用户
//没有客户端和服务器之分
enum{UPORT = 8880};//固定连接端口
enum{ //命令号
SEND_HELLO = 0x1234, //局域网内某用户上线后,会向其他主机发送该命令(打招呼)
REPL_HELLO, //收到打招呼信息后,回复该命令
SEND_BYE, //当某主机下线后,会向其他主机发送该命令
SEND_TEXT, //发送文字的命令
SEND_FILE //发送文件的命令
};
struct SInfo //存放本机的信息,主机名,用户名,IP地址,用户组
{
char sHost[16];
char sName[16];
char sIp[16];
char sGroup[16];
};
struct SPack//作为接受/发送的数据包,命令号+数据,防止丢包
{
int nCmd;
char sData[1020];
};
struct SText//文字信息,包含主机名和文字
{
char sName[16];
char sText[256];
};
//APP初始化程序中需要完成的事情:
SInfo & info = m_info; //定义一个本机信息结构体的引用
gethostname(info.sHost,sizeof(info.sHost));//获取本机名
//int gethostname (char *name, int namelen ); 缓冲区以及其长度,返回0表示获取成功,返回SOCKET_ERROR表示失败,WSAGetLastError(),获取失败代码
hostent* pHost = gethostbyname(info.sHost);//获取与该主机名相对应的主机信息,返回一个结构体
/*
struct hostent {
char FAR *h_name; //主机名
char FAR *FAR *h_aliases; //域名的别名
short h_addrtype; //返回地址的类型IP4 or IP6
short h_length; //地址字节数4 or 6
char FAR * FAR * h_addr_list; //主机地址列表,该值是网络字节序,一般取第一个
};
*/
in_addr* addr = (in_addr*)pHost->h_addr;//调用返回结构体中的IP地址,该值是网络字节序
//这里用了类型转换,h_addr_list[0] 就是h_addr,他指向的in_addr结构体的地址值,他是个指针的指针
strcpy(info.sIp,inet_ntoa(*addr));//网络字节转字符串,并复制到本机信息结构体中
DWORD dw = sizeof(info.sName);
GetUserName(info.sName,&dw);//获取本机用户名
/*
BOOL GetUserName(
LPTSTR lpBuffer, // name bufferm用户名缓冲区
LPDWORD nSize // size of name buffer 缓冲区大小的地址,DWORD型
);
*/
CString sIP;
SPack pack = {SEND_HELLO};
memcpy(pack.sData,&info,sizeof(SInfo));//将info里的信息放入发送包里
//关于复制内存
//memcpy、strcpy 都是拷贝内存,但前者根据长度,后者拷贝到'\0'while(i<255)
//向局域网内其他253个主机打招呼,该方式运行速度较慢
while(i<255)
{
sIP.Format("192.168.1.%d",i);//若接收方没有该软件接受则接受程序会返回一个错误码
m_sock.SendTo(&pack,sizeof(int)+sizeof(SInfo),UPORT,sIP);
++i;
}
/*
int SendTo(
const void* lpBuf, //发送数据的缓冲区
int nBufLen, //缓冲区待发送的字节数
UINT nHostPort, //端口号
LPCTSTR lpszHostAddress = NULL, //连接到的套接字的网络地址
int nFlags = 0 ); //设置函数调用方式
*/
//该方式运行速度快
sIP = info.sIp;//通过广播地址发送。
int i=sIP.ReverseFind('.');//找到字符串中最后一个.号的位置,从0 开始192.168.1.即9,这样做的前提是不知道IP地址
sIP = sIP.Left(i+1)+"255";//选取该字符串左十位,与"255"合并,构成广播地址。
m_sock.SendTo(&pack,sizeof(int)+sizeof(SInfo),UPORT,sIP);//发送给广播地址,广播地址群发给局域网内所有主机
//对话框初始函数,设置列表控件的风格:
m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);//整行选取+网格线
//信息接收函数,重载了基类CSocket::OnReceive(nErrorCode)
void CSocketu::OnReceive(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
SPack pack={0};//定义一个pack接受数据
CString sIP;
UINT nPort;
int nRet = ReceiveFrom(&pack,sizeof(pack),sIP,nPort);//第二个参数为缓冲区的长度,有别于发送函数
if(nRet <= 0)//接受失败,若对方主机没有正确接收的数据包(对方主机没有安装该软件)也会返回也一个错误码:c10054
{
int nError = GetLastError();
return;
}
CUdpsocketDlg *pDlg = (CUdpsocketDlg*)AfxGetMainWnd();
switch(pack.nCmd)
{
case SEND_HELLO://如果是有主机上线的消息
{
pDlg->InsertHost(pack);//插入列表控件
SInfo& info = (SInfo&)pack.sData;
if(!strcmp(info.sHost,theApp.m_info.sHost))//判断是否是本机发来的消息
return;
pack.nCmd = REPL_HELLO;//将发来的消息包中的内容替换成本机的信息
info = theApp.m_info;
SendTo(&pack,sizeof(int)+sizeof(SInfo),nPort,sIP);//替换之后发回去
}
break;
case REPL_HELLO:
pDlg ->InsertHost(pack);//接受到回复后插入列表
break;
case SEND_BYE:
pDlg->RemoveHost(sIP);//有用户退出则移除他在列表控件上的名字
break;
case SEND_TEXT:
pDlg->OnText(pack);
break;
}
// CSocket::OnReceive(nErrorCode);
}
//往列表控件中插入上线主机信息
void CUdpsocketDlg::InsertHost(SPack &pack)
{
SInfo& info = (SInfo&)pack.sData;
//将SDate强制转换为SInfo的一个引用,sDate里就存了一个info的结构体大小的内容
CString sHost = info.sHost;
int i = -1;
int nCount = m_list.GetItemCount();//获取控件中信息行数
while(++i<nCount)//遍历列表,如果当前列表存在该主机名,则退出
{
if(m_list.GetItemText(i,0) == sHost)
break;
}
if(i == nCount)//若不存在,则插入
{
m_list.InsertItem(i,info.sHost);
m_list.SetItemText(i,1,info.sName);
m_list.SetItemText(i,2,info.sIp);
}
}
//双击列表控件中的信息,弹出一个聊天对话框
void CUdpsocketDlg::OnDblclkList(NMHDR* pNMHDR, LRESULT* pResult) //定义一个双击的消息响应
{
// TODO: Add your control notification handler code here
NM_LISTVIEW* p = (NM_LISTVIEW*) pNMHDR; //参数pNMHDR中包含了点击项的在列表中的位置信息,转为NM_LISTVIEW*类型
/*
typedef struct tagNMLISTVIEW
{
NMHDR hdr;
int iItem;
int iSubItem;
UINT uNewState;
UINT uOldState;
UINT uChanged;
POINT ptAction;
LPARAM lParam;
} NMLISTVIEW, FAR *LPNMLISTVIEW;
*/
int nSel = p->iItem; //获取选中的行数
if(nSel<0) //未选中
return;
CChatDlg *pDlg = (CChatDlg *)m_list.GetItemData(nSel);//该函数获取列表中该项信息的附加信息,一旦对该项有过操作,该附加信息就不为空
if(!pDlg) //若没有点击过该项
{
pDlg = new CChatDlg; // 创建一个聊天对话框对象
SInfo& info = pDlg->m_info;
m_list.GetItemText(nSel,0,info.sHost,sizeof(info.sHost)); //获取该项的中每一列的内容,放进info中
m_list.GetItemText(nSel,1,info.sName,sizeof(info.sName));
m_list.GetItemText(nSel,2,info.sIp,sizeof(info.sIp));
pDlg->Create(IDD_CHATDLG,GetDesktopWindow());//创建非模式对话框(该对话框未关闭前可以对其他对话框的操作),父窗口为桌面窗口
//GetDesktopWindow()返回桌面窗口的句柄
m_list.SetItemData(nSel,(DWORD)pDlg);//将该信息附加到对应项上
}
pDlg->ShowWindow(SW_SHOW);//以当前大小和位置显示对话框
pDlg->SetForegroundWindow();//将该窗口设置为前台窗口
*pResult = 0;
}
//发送消息消息响应
void CChatDlg::OnOK()
{
// TODO: Add extra validation here
SPack pack={SEND_TEXT};
SText& text = (SText &)pack.sData;//强制转为text引用,因为发送文字只需SText结构体大小
int nSel = GetDlgItemText(IDC_SEND,text.sText,sizeof(text.sText));//返回复制到缓冲区的文字个数
if(nSel<=0)//获取发送控件里的文字
{
AfxMessageBox("不能发送空文字!");
return;
}
strcpy(text.sName,theApp.m_info.sHost);
theApp.m_sock.SendTo(&pack,sizeof(int)+sizeof(SText),UPORT,m_info.sIp);//发送本机名和文字
COleDateTime time = COleDateTime::GetCurrentTime();
CString str;//将自己发的文字显示界面
str.Format("你对 %s 说: (%02d:%02d:%02d) \r\n%s\r\n",m_info.sHost,
time.GetHour(),time.GetMinute(),time.GetSecond(),text.sText);
CEdit *pEdit = (CEdit*)GetDlgItem(IDC_LIST);//获取列表控件的句柄
pEdit->SetSel(pEdit->GetWindowTextLength(),-1);
pEdit->ReplaceSel(str);
SetDlgItemText(IDC_SEND,"");
GetDlgItem(IDC_SEND)->SetFocus();
//CDialog::OnOK();
}
//接受到文字时的处理函数
void CUdpsocketDlg::OnText(SPack &pack)
{
SText &text = (SText&)pack.sData;//强制引用
int i = -1;
int nCount = m_list.GetItemCount();
while(++i<nCount)
{
if(m_list.GetItemText(i,0) == text.sName)//如果列表中存在该主机
{
CChatDlg * pDlg = (CChatDlg*)m_list.GetItemData(i);//获取该项的附件信息
if(!pDlg)//如果消息为空,即没有打开过该聊天窗口
{
pDlg = new CChatDlg;//创建新的对话框
SInfo& info = pDlg->m_info;
m_list.GetItemText(i,0,info.sHost,sizeof(info.sHost));
m_list.GetItemText(i,1,info.sName,sizeof(info.sName));
m_list.GetItemText(i,2,info.sIp,sizeof(info.sIp));
pDlg->Create(IDD_CHATDLG,GetDesktopWindow());
m_list.SetItemData(i,(DWORD)pDlg);
}
pDlg->ShowWindow(SW_SHOW);
pDlg->SetForegroundWindow();
pDlg->FlashWindow(true);//使通知栏窗口闪烁
CString str;
COleDateTime time = COleDateTime::GetCurrentTime();
str.Format("%s 对你说 %d:%d:%d \r\n %s \r\n",text.sName,time.GetHour(),time.GetMinute(),time.GetSecond(),text.sText);
CEdit* pEdit = (CEdit *)pDlg->GetDlgItem(IDC_LIST1);
pEdit->SetSel(pEdit->GetWindowTextLength(),-1);
pEdit->ReplaceSel(str);
return;
}
}
}
//App退出函数
int CUdpsocketApp::ExitInstance() //退出进程函数
{
SPack pack = {SEND_BYE};
CString sIP = m_info.sIp;
int i = sIP.ReverseFind('.');
sIP = sIP.Left(i+1)+"255";
m_sock.SendTo(&pack,sizeof(int),UPORT,sIP);//发送BYE消息到局域网内其他主机
return CWinApp::ExitInstance();
}
//删除该IP所占的一行
void CUdpsocketDlg::RemoveHost(CString szIP)
{
int i = 0;
int nCount = m_list.GetItemCount();
while(i++<nCount)
{
CString s = m_list.GetItemText(i,2);
if(m_list.GetItemText(i,2) == szIP)
{
m_list.DeleteItem(i);
}
}
}